Browse code
feat(docs): add initial documentation
Edward Langley authored on 15/03/2022 11:13:16
Showing 2 changed files
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 |
} |