git.fiddlerwoaroof.com
Browse code

feat(docs): add initial documentation

Edward Langley authored on 15/03/2022 11:13:16
Showing 2 changed files
... ...
@@ -8,6 +8,68 @@ https://github.com/sbcl/specializable). For the moment, this is only
8 8
 used to provide a Shape specializer, as the details of the interaction
9 9
 between such specializers and subtyping are an open question.
10 10
 
11
-* Use examples:
11
+* Docs
12 12
 
13
-** A React Component 
13
+** Basic Usage
14
+
15
+#+NAME: imports
16
+#+BEGIN_SRC js2
17
+import { defgeneric } from "./genfuns.js";
18
+#+END_SRC
19
+
20
+Defining a function works by calling src_js2{defgeneric} with some
21
+information about the function name and arguments. Methods are then
22
+added by calling the appropriate methods with a pair of arguments: a
23
+list of specializers (prototypes in the simple case, although there
24
+are other options) and a function to run if those specializers match.
25
+
26
+#+NAME: basic-definition
27
+#+BEGIN_SRC js2
28
+  const example1generic = defgeneric("example1", "a", "b")
29
+    .primary([Number, Object], (n, __) => [1, n])
30
+    .primary([Object, Number], (_, n) => [2, n])
31
+    .primary([Object, Object], (_, __) => [5, null]);
32
+#+END_SRC
33
+
34
+After a generic function has been defined, you can get the function to
35
+call it by accessing its src_js2{.fn} attribute.
36
+
37
+#+NAME: call-the-function
38
+#+BEGIN_SRC js2
39
+  const example1 = example1generic.fn;
40
+
41
+  expect(example1(5, {})).toEqual([1, 5]);
42
+  expect(example1({}, 6)).toEqual([2, 6]);
43
+  expect(example1("hello", {})).toEqual([5, null]);
44
+  expect(example1({}, "world")).toEqual([5, null]);
45
+  expect(example1({}, {})).toEqual([5, null]);
46
+#+END_SRC
47
+
48
+If a separate reference to the generic function object is maintained,
49
+you can add methods like so:
50
+
51
+#+NAME: add-methods
52
+#+BEGIN_SRC js2
53
+  example1generic
54
+    .primary([String, Object], (s, __) => [3, s])
55
+    .primary([Object, String], (_, s) => [4, s]);
56
+
57
+  expect(example1("hello", {})).toEqual([3, "hello"]);
58
+  expect(example1({}, "world")).toEqual([4, "world"]);
59
+#+END_SRC
60
+
61
+#+BEGIN_SRC js :tangle src/doc.test.js :comments noweb :noweb tangle :exports none
62
+  <<imports>>
63
+
64
+  describe("defgeneric", () => {
65
+    test("methods get called appropriately", () => {
66
+      <<basic-definition>>
67
+
68
+      <<call-the-function>>
69
+
70
+      <<add-methods>>
71
+
72
+      <<sample1>>
73
+    });
74
+  });
75
+#+END_SRC
... ...
@@ -34,6 +34,9 @@ const before_qualifier = Symbol.for("before");
34 34
 const after_qualifier = Symbol.for("after");
35 35
 const around_qualifier = Symbol.for("around");
36 36
 
37
+/**
38
+ * The base prototype for a method.
39
+ */
37 40
 const Method = {
38 41
   lambda_list: [],
39 42
   qualifiers: [],
... ...
@@ -42,6 +45,7 @@ const Method = {
42 45
   generic_function: null,
43 46
 };
44 47
 
48
+/** @lends GenericFunction.prototype */
45 49
 let genfun_prototype = {
46 50
   name: "(placeholder)",
47 51
   lambda_list: [],
... ...
@@ -50,15 +54,62 @@ let genfun_prototype = {
50 54
     ensure_method(this, this.lambda_list, qualifiers, specializers, body);
51 55
     return this;
52 56
   },
57
+  /**
58
+   *  Add a primary method to the generic function. In the context of
59
+   *  the body, `this` has the available functions `call_next_method`
60
+   *  and the property `next_method_p` which allow delegating to other
61
+   *  implementations of the generic function. Only one of these will
62
+   *  be executed, unless `call_next_method` is called.
63
+   *
64
+   *  @param {Specializer[]} specializers the specializers controlling
65
+   *                                      dispatch to this method
66
+   *  @param {Function} body the implementation of the method
67
+   *  @returns GenericFunction
68
+   */
53 69
   primary(specializers, body) {
54 70
     return this.method([], specializers, body);
55 71
   },
72
+  /**
73
+   *  Add a before method to the generic function. Every before method
74
+   *  is runs before the primary method and no before method can
75
+   *  influence the return value of the generic function.
76
+   *
77
+   *  @param {Specializer[]} specializers the specializers controlling dispatch to this method
78
+   *  @param {Function} body the implementation of the method
79
+   *  @returns GenericFunction
80
+   */
56 81
   before(specializers, body) {
57 82
     return this.method([before_qualifier], specializers, body);
58 83
   },
84
+  /**
85
+   *  Add a after method to the generic function. Every after method
86
+   *  is runs after the primary method and no after method can
87
+   *  influence the return value of the generic function.
88
+   *
89
+   *  @param {Specializer[]} specializers the specializers controlling dispatch to this method
90
+   *  @param {Function} body the implementation of the method
91
+   *  @returns GenericFunction
92
+   */
59 93
   after(specializers, body) {
60 94
     return this.method([after_qualifier], specializers, body);
61 95
   },
96
+  /**
97
+   *  Add a around method to the generic function. In the context of
98
+   *  the body, `this` has the available functions `call_next_method`
99
+   *  and the property `next_method_p` which allow delegating to other
100
+   *  implementations of the generic function. A generic function with
101
+   *  only around methods cannot be called.  However, an around method
102
+   *  can skip the invocation of the primary method by not calling
103
+   *  `call_next_method` in its body.  An around method can also
104
+   *  modify the results of the primary method and/or the arguments to
105
+   *  the primary method. Note that changing the arguments to the
106
+   *  primary method in a way that violates the specializers is
107
+   *  unsupported and may have surprising consequences.
108
+   *
109
+   *  @param {Specializer[]} specializers the specializers controlling dispatch to this method
110
+   *  @param {Function} body the implementation of the method
111
+   *  @returns GenericFunction
112
+   */
62 113
   around(specializers, body) {
63 114
     return this.method([around_qualifier], specializers, body);
64 115
   },
... ...
@@ -77,7 +128,6 @@ let genfun_prototype = {
77 128
 
78 129
 /**
79 130
  * @class
80
- * @extends genfun_prototype
81 131
  * @param {string} name
82 132
  * @param {string[]} lambda_list
83 133
  * @property {Method[]} methods
... ...
@@ -91,9 +141,34 @@ function GenericFunction(name, lambda_list) {
91 141
   this.lambda_list = lambda_list;
92 142
   this.methods = [];
93 143
 }
94
-
95 144
 GenericFunction.prototype = Object.create(genfun_prototype);
96 145
 
146
+/**
147
+ * The main entrypoint to the library. Code like this constructs a new
148
+ * generic function named `foo`:
149
+ *
150
+ * ```js
151
+ * const foo = defgeneric('foo', 'arg1', 'arg2', 'arg3');
152
+ * ```
153
+ *
154
+ * To add methods to the generic function, you grab the generic
155
+ * function reference and call the relevant methods (arrow functions
156
+ * may be used, unless you want access to `call_next_method`):
157
+ *
158
+ * ```js
159
+ * foo.primary([String, Object, Object], (arg1, arg2, arg3) => { ... });
160
+ * foo.around([String, Object, Object], function (arg1, arg2, arg3) {
161
+ *   if (arg1 !== 'a') {
162
+ *     return this.call_next_method();
163
+ *   }
164
+ * });
165
+ * ```
166
+ *
167
+ * Note that these names mostly exist for introspection
168
+ * purposes. These were used in custom formatters, but Chrome removed
169
+ * that functionality.
170
+ *
171
+ */
97 172
 export function defgeneric(name, ...argument_names) {
98 173
   return GenericFunction(name, argument_names);
99 174
 }