git.fiddlerwoaroof.com
Browse code

refactoring

Ed Langley authored on 07/09/2018 00:30:25
Showing 12 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+{
2
+    "presets": ["@babel/env"],
3
+    "plugins": ["@babel/plugin-proposal-object-rest-spread"],
4
+}
... ...
@@ -1,3 +1,3 @@
1 1
 node_modules
2 2
 /dist
3
-/dist
3
+*~
... ...
@@ -3,8 +3,19 @@
3 3
     <head>
4 4
         <meta charset="UTF-8"/>
5 5
         <title>Document</title>
6
+        <style>
7
+            the-name {font-weight: bold}
8
+            the-address {font-style: italic}
9
+        </style>
6 10
     </head>
7 11
     <body>
8
-        <script src="./genfuns.js"></script>
12
+        <section class="root">
13
+            <the-name></the-name>
14
+            <the-address></the-address>
15
+        </section>
16
+        <script>var exports = {};</script>
17
+        <script src="./dist/genfuns.js"></script>
18
+        <script src="./dist/genfun_formatter.js"></script>
19
+        <script src="./dist/main.js"></script>
9 20
     </body>
10 21
 </html>
11 22
deleted file mode 100644
... ...
@@ -1,183 +0,0 @@
1
-let genfun_prototype = {
2
-    name: "(placeholder)",
3
-    lambda_list: [],
4
-    methods: [],
5
-};
6
-
7
-function GenericFunction(name, lambda_list, methods) {
8
-    if (! (this instanceof GenericFunction) ) {
9
-        return new GenericFunction(name, lambda_list, methods);
10
-    }
11
-
12
-    this.name = name;
13
-    this.lambda_list = lambda_list;
14
-    this.methods = methods;
15
-}
16
-
17
-GenericFunction.prototype = Object.assign(
18
-    Object.create(genfun_prototype), {
19
-    }
20
-);
21
-
22
-let method_prototype = {
23
-    lambda_list: [],
24
-    qualifiers: [],
25
-    specializrs: [],
26
-    body() { throw new Error('Unimplemented'); },
27
-    environment: {},
28
-    generic_function: {},
29
-};
30
-
31
-function StandardMethod(
32
-    lambda_list, qualifiers, specializers, body, environment, generic_function
33
-) {
34
-    if (! (this instanceof StandardMethod) ) {
35
-        return new StandardMethod(...arguments);
36
-    }
37
-
38
-    this.lambda_list = lambda_list;
39
-    this.qualifiers = qualifiers;
40
-    this.specializers = specializers;
41
-    this.body = body;
42
-    this.environment = environment;
43
-    this.generic_function = generic_function;
44
-}
45
-
46
-function ensure_method(gf, ...rest) {
47
-    let new_method = StandardMethod(...rest);
48
-    add_method(gf, new_method);
49
-    return new_method;
50
-}
51
-
52
-function add_method(gf, method) {
53
-    method.generic_function = gf;
54
-    gf.methods.push(method);
55
-    return method;
56
-}
57
-
58
-function classes_of(args) {
59
-    return args.map(Object.getPrototypeOf);
60
-}
61
-
62
-const required_portion = x => x;
63
-
64
-function apply_generic_function(gf, args) {
65
-    let applicable_methods =
66
-        compute_applicable_methods_using_classes(gf, required_portion(args));
67
-    if (applicable_methods.length === 0) {
68
-        throw new Error(`no applicable methods for gf ${gf.name} with args ${args}`);
69
-    } else {
70
-        return apply_methods(gf, args, applicable_methods);
71
-    }
72
-}
73
-
74
-function method_more_specific_p(m1, m2, required_classes) {
75
-    const m1specializers = m1.specializers;
76
-    const m2specializers = m2.specializers;
77
-
78
-    for ([spec1, spec2] of m1specializers.map((el, idx) => [el, m2specializers[idx]])) {
79
-        if (spec1 !== spec2) {
80
-            return sub_specializer_p(spec1, spec2);
81
-        }
82
-    }
83
-}
84
-
85
-function sub_specializer_p(c1, c2) {
86
-    return c1.isPrototypeOf(c2);
87
-}
88
-
89
-const idS = Symbol.for('id');
90
-Object.prototype[idS] = function () { return this };
91
-
92
-function compute_applicable_methods_using_classes(gf, required_classes) {
93
-    const applicable_methods = gf.methods.filter(
94
-        method =>
95
-            method.specializers.every((specializer,idx) => (console.info(specializer),
96
-                                                            specializer.prototype.isPrototypeOf(required_classes[idx])
97
-                                                            || specializer.prototype.isPrototypeOf(required_classes[idx][idS]())))
98
-    );
99
-
100
-    applicable_methods.sort((a,b) => {
101
-        if (method_more_specific_p(a,b)) {
102
-            return -1;
103
-        }
104
-        if (method_more_specific_p(b,a)) {
105
-            return 1;
106
-        }
107
-        return 0;
108
-    })
109
-
110
-    return applicable_methods;
111
-}
112
-
113
-const before_qualifier = Symbol.for('before');
114
-const after_qualifier = Symbol.for('after');
115
-
116
-function arr_eq(a1, a2) {
117
-    if (a1.length !== a2.length) {
118
-        return false;
119
-    } else {
120
-        for (let x = 0; x < a1.length; x++) {
121
-            if (a1[x] instanceof Array && a2[x] instanceof Array) {
122
-                if (!arr_eq(a1[x], a2[x])) {
123
-                    return false;
124
-                }
125
-            } else if (a1[x] !== a2[x]) {
126
-                return false;
127
-            }
128
-        }
129
-        return true;
130
-    }
131
-}
132
-
133
-const primary_method_p = method => method.qualifiers.length === 0;
134
-const before_method_p = method => arr_eq(method.qualifiers, [before_qualifier]);
135
-const after_method_p = method => arr_eq(method.qualifiers, [after_qualifier]);
136
-
137
-function apply_methods(gf, args, applicable_methods) {
138
-    const primaries = applicable_methods.filter(primary_method_p);
139
-    const befores = applicable_methods.filter(before_method_p);
140
-    const afters = applicable_methods.filter(after_method_p);
141
-    afters.reverse();
142
-
143
-    if (primaries.length === 0) {
144
-        throw new Error(`No primary method for ${gf.name}`);
145
-    }
146
-
147
-    for (let before of befores) {
148
-        apply_method(before, args, []);
149
-    }
150
-
151
-    const result = apply_method(primaries[0], args, primaries.slice(1));
152
-
153
-    for (let after of afters) {
154
-        apply_method(after, args, []);
155
-    }
156
-
157
-    return result;
158
-}
159
-
160
-function apply_method(method, args, next_methods) {
161
-    return Function('call_next_method', 'next_method_p', `return ${method.body.toString()}`)(
162
-        (...cnm_args) => {
163
-            if (next_methods.length === 0) {
164
-                throw new Error(`no next method for genfun ${method.generic_function.name}`);
165
-            }
166
-
167
-            return apply_methods(method.generic_function, cnm_args.length > 0 ? cnm_args : args, next_methods);
168
-        },
169
-        () => next_methods.length === 0
170
-    )(...args);
171
-}
172
-
173
-const gf = GenericFunction("foobar", ["a", "b"], []);
174
-ensure_method(
175
-    gf,
176
-    [], [], [Object, Array], (thing, arr) => [thing, ...arr], null
177
-);
178
-ensure_method(
179
-    gf,
180
-    [], [], [Object, Object], (thing, single) => [thing, single], null
181
-);
182
-
183
-console.info(apply_generic_function(gf, [["asdf"],2]));
184 0
new file mode 100644
... ...
@@ -0,0 +1,73 @@
1
+
2
+window.devtoolsFormatters = [
3
+    {
4
+        header(obj) {
5
+            if (obj instanceof StandardMethod) {
6
+            console.log('........................................')
7
+                const method = obj;
8
+                return ["div", {}, `#<StandardMethod ${method.qualifiers.map(x => ':'+x.toString().slice(7,-1)).join(' ')} (${method.specializers.map(x => x.name.toString())})>`];
9
+            }
10
+            return null;
11
+        },
12
+        hasBody() { return false; }
13
+    },
14
+    {
15
+        header(obj, config) {
16
+            if (config && config.genfunFormatter) {
17
+                return ["div", {}, config.key];
18
+            } else if (! (obj.gf || obj instanceof GenericFunction) ) {
19
+                return null;
20
+            } else if (obj.gf) {
21
+                const args = obj.gf.lambda_list.join(', ');
22
+                const method_count = obj.gf.methods.length
23
+                return [
24
+                    'div', {},
25
+                    `GenericFunction lambda: ${obj.gf.name}(${args}) `
26
+                        + `[${method_count} methods]`
27
+                ];
28
+            } else {
29
+                const args = obj.lambda_list.join(', ');
30
+                const method_count = obj.methods.length
31
+                return [
32
+                    'div', {},
33
+                    `#<GenericFunction: ${obj.name}(${args}) [${method_count} methods]>`
34
+                ];
35
+            }
36
+        },
37
+        hasBody(obj) {return obj instanceof GenericFunction || obj instanceof StandardMethod;},
38
+        body(obj, config) {
39
+            if (! (obj instanceof GenericFunction || obj instanceof StandardMethod) ) {
40
+                return null;
41
+            } else if ( obj instanceof StandardMethod ) {
42
+                return ["div", {style: 'margin-left: 2em'}].concat(
43
+                    Object.keys(obj).map(
44
+                        key => {
45
+                            if (obj[key] instanceof String) {
46
+                                return ["div", {}, `${key}: ${obj[key]},`];
47
+                            } else {
48
+                                return ["div", {}, `${key}: `, ["object", {object: obj[key]}], ','];
49
+                            }
50
+                        }
51
+                    )
52
+                );
53
+            }
54
+
55
+            const children = obj.methods.map(
56
+                (method,idx) => {
57
+                    const child = [
58
+                        "object", {
59
+                            object: method,
60
+                            config: {
61
+                                genfunFormatter: true,
62
+                                key: `#<StandardMethod ${method.qualifiers.map(x => ':'+x.toString().slice(7,-1)).join(' ')} (${method.specializers.map(x => x.name.toString())})>`,
63
+                            },
64
+                        },
65
+                    ];
66
+                    return ["div", {style: `margin-left: 2em;`},
67
+                            child]
68
+                }
69
+            );
70
+            return ["div", {}].concat(children);
71
+        }
72
+    }
73
+];
0 74
new file mode 100644
... ...
@@ -0,0 +1,62 @@
1
+
2
+window.devtoolsFormatters = [
3
+    {
4
+        header(obj, config) {
5
+            if (config && config.genfunFormatter) {
6
+                return ["div", {}, config.key];
7
+            } else if (! (obj.gf || obj instanceof GenericFunction) ) {
8
+                return null;
9
+            } else if (obj.gf) {
10
+                const args = obj.gf.lambda_list.join(', ');
11
+                const method_count = obj.gf.methods.length
12
+                return [
13
+                    'div', {},
14
+                    `GenericFunction lambda: ${obj.gf.name}(${args}) `
15
+                        + `[${method_count} methods]`
16
+                ];
17
+            } else {
18
+                const args = obj.lambda_list.join(', ');
19
+                const method_count = obj.methods.length
20
+                return [
21
+                    'div', {},
22
+                    `#<GenericFunction: ${obj.name}(${args}) [${method_count} methods]>`
23
+                ];
24
+            }
25
+        },
26
+        hasBody(obj) {return obj instanceof GenericFunction || obj instanceof StandardMethod;},
27
+        body(obj, config) {
28
+            if (! (obj instanceof GenericFunction || obj instanceof StandardMethod) ) {
29
+                return null;
30
+            } else if ( obj instanceof StandardMethod ) {
31
+                return ["div", {style: 'margin-left: 2em'}].concat(
32
+                    Object.keys(obj).map(
33
+                        key => {
34
+                            if (obj[key] instanceof String) {
35
+                                return ["div", {}, `${key}: ${obj[key]},`];
36
+                            } else {
37
+                                return ["div", {}, `${key}: `, ["object", {object: obj[key]}], ','];
38
+                            }
39
+                        }
40
+                    )
41
+                );
42
+            }
43
+
44
+            const children = obj.methods.map(
45
+                (method,idx) => {
46
+                    const child = [
47
+                        "object", {
48
+                            object: method,
49
+                            config: {
50
+                                genfunFormatter: true,
51
+                                key: `#<StandardMethod ${method.qualifiers.map(x => ':'+x.toString().slice(7,-1)).join(' ')} (${method.specializers.map(x => x.name.toString())})>`,
52
+                            },
53
+                        },
54
+                    ];
55
+                    return ["div", {style: `margin-left: 2em;`},
56
+                            child]
57
+                }
58
+            );
59
+            return ["div", {}].concat(children);
60
+        }
61
+    }
62
+];
0 63
new file mode 100644
... ...
@@ -0,0 +1,243 @@
1
+const before_qualifier = Symbol.for('before');
2
+const after_qualifier = Symbol.for('after');
3
+const around_qualifier = Symbol.for('around');
4
+
5
+
6
+let genfun_prototype = {
7
+    name: "(placeholder)",
8
+    lambda_list: [],
9
+    methods: [],
10
+    method(qualifiers, specializers, body) {
11
+        ensure_method(this, this.lambda_list, qualifiers, specializers, body)
12
+        return this;
13
+    },
14
+    primary(specializers, body) {
15
+        return this.method([], specializers, body);
16
+    },
17
+    before(specializers, body) {
18
+        return this.method([before_qualifier], specializers, body);
19
+    },
20
+    after(specializers, body) {
21
+        return this.method([after_qualifier], specializers, body);
22
+    },
23
+    around(specializers, body) {
24
+        return this.method([around_qualifier], specializers, body);
25
+    },
26
+    get fn() {
27
+        const gf = this;
28
+        const lambda = (function() {
29
+            return apply_generic_function(gf, [].slice.call(arguments));
30
+        }).bind(gf);
31
+        return Object.defineProperties(lambda, {
32
+            'name': {value: gf.name},
33
+            'lambda_list': {value: gf.lambda_list},
34
+            'gf': {value: gf},
35
+        });
36
+    }
37
+};
38
+
39
+function GenericFunction(name, lambda_list) {
40
+    if (! (this instanceof GenericFunction) ) {
41
+        return new GenericFunction(...arguments);
42
+    }
43
+
44
+    this.name = name;
45
+    this.lambda_list = lambda_list;
46
+    this.methods = [];
47
+}
48
+
49
+GenericFunction.prototype = Object.create(genfun_prototype);
50
+
51
+export function defgeneric(name, ...argument_names) {
52
+    return GenericFunction(name, argument_names);
53
+}
54
+
55
+let method_prototype = {
56
+    lambda_list: [],
57
+    qualifiers: [],
58
+    specializrs: [],
59
+    body() { throw new Error('Unimplemented'); },
60
+    environment: {},
61
+    generic_function: {},
62
+};
63
+
64
+function StandardMethod(
65
+    lambda_list, qualifiers, specializers, body
66
+) {
67
+    if (! (this instanceof StandardMethod) ) {
68
+        return new StandardMethod(...arguments);
69
+    }
70
+
71
+    this.lambda_list = lambda_list;
72
+    this.qualifiers = qualifiers;
73
+    this.specializers = specializers;
74
+    this.body = body;
75
+    this.generic_function = null;
76
+}
77
+
78
+function ensure_method(gf, lambda_list, qualifiers, specializers, body) {
79
+    let new_method = StandardMethod(...[].slice.call(arguments, 1));
80
+    add_method(gf, new_method);
81
+    return new_method;
82
+}
83
+
84
+function add_method(gf, method) {
85
+    method.generic_function = gf;
86
+    gf.methods.push(method);
87
+    return method;
88
+}
89
+
90
+function classes_of(args) {
91
+    return args.map(Object.getPrototypeOf);
92
+}
93
+
94
+const required_portion = x => x;
95
+
96
+function apply_generic_function(gf, args) {
97
+    let applicable_methods =
98
+        compute_applicable_methods_using_classes(gf, required_portion(args));
99
+    if (applicable_methods.length === 0) {
100
+        throw new Error(`no applicable methods for gf ${gf.name} with args ${JSON.stringify(args)}`);
101
+    } else {
102
+        return apply_methods(gf, args, applicable_methods);
103
+    }
104
+}
105
+
106
+function method_more_specific_p(m1, m2, required_classes) {
107
+    const m1specializers = m1.specializers;
108
+    const m2specializers = m2.specializers;
109
+
110
+    for (let [spec1, spec2] of m1specializers.map((el, idx) => [el, m2specializers[idx]])) {
111
+        if (spec1 !== spec2) {
112
+            return sub_specializer_p(spec1, spec2);
113
+        }
114
+    }
115
+}
116
+
117
+function sub_specializer_p(c1, c2) {
118
+    return c1.isPrototypeOf(c2);
119
+}
120
+
121
+const idS = Symbol.for('id');
122
+Object.prototype[idS] = function () { return this };
123
+
124
+export function matchesSpecializer(obj, specializer) {
125
+    let result = obj === specializer.prototype;
126
+    let objType = typeof obj;
127
+
128
+    if (!result && objType === 'object') {
129
+        result = Object.isPrototypeOf.call(specializer.prototype, obj);
130
+    } else if (objType === 'number') {
131
+        result = matchesSpecializer(Number.prototype, specializer) || matchesSpecializer(specializer.prototype, Number);
132
+    } else if (objType === 'string') {
133
+        result = matchesSpecializer(String.prototype, specializer) || matchesSpecializer(specializer.prototype, String);
134
+    }
135
+
136
+    return result;
137
+}
138
+
139
+
140
+function compute_applicable_methods_using_classes(gf, required_classes) {
141
+    const applicable_methods = gf.methods.filter(
142
+        method => method.specializers.every((specializer, idx) => matchesSpecializer(required_classes[idx], specializer))
143
+    );
144
+
145
+    applicable_methods.sort((a,b) => {
146
+        if (method_more_specific_p(a,b)) {
147
+            return 1;
148
+        }
149
+        if (method_more_specific_p(b,a)) {
150
+            return -1;
151
+        }
152
+        return 0;
153
+    })
154
+
155
+    return applicable_methods;
156
+}
157
+
158
+
159
+function arr_eq(a1, a2) {
160
+    if (a1.length !== a2.length) {
161
+        return false;
162
+    } else {
163
+        for (let x = 0; x < a1.length; x++) {
164
+            if (a1[x] instanceof Array && a2[x] instanceof Array) {
165
+                if (!arr_eq(a1[x], a2[x])) {
166
+                    return false;
167
+                }
168
+            } else if (a1[x] !== a2[x]) {
169
+                return false;
170
+            }
171
+        }
172
+        return true;
173
+    }
174
+}
175
+
176
+const primary_method_p =
177
+      method => method instanceof WrappedMethod || method.qualifiers.length === 0;
178
+const before_method_p =
179
+      method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [before_qualifier]);
180
+const after_method_p =
181
+      method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [after_qualifier]);
182
+const around_method_p =
183
+      method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [around_qualifier]);
184
+
185
+function WrappedMethod(continuation) {
186
+    this.continuation = continuation;
187
+}
188
+
189
+function apply_methods(gf, args, applicable_methods) {
190
+    const primaries = applicable_methods.filter(primary_method_p);
191
+    const befores = applicable_methods.filter(before_method_p);
192
+    const arounds = applicable_methods.filter(around_method_p);
193
+    const afters = applicable_methods.filter(after_method_p);
194
+    afters.reverse();
195
+
196
+    const main_call = Object.defineProperty(
197
+        function() {
198
+            if (primaries.length === 0) {
199
+                throw new Error(`No primary method for ${gf.name}`);
200
+            }
201
+
202
+            for (let before of befores) {
203
+                apply_method(before, args, []);
204
+            }
205
+
206
+            try {
207
+                return apply_method(primaries[0], args, primaries.slice(1));
208
+            } finally {
209
+                for (let after of afters) {
210
+                    apply_method(after, args, []);
211
+                }
212
+            }
213
+        },
214
+        'name', {value: `main_call_${gf.name}`}, 
215
+    );
216
+
217
+    if (arounds.length === 0) {
218
+        return main_call();
219
+    } else {
220
+        const wrapped_main_call = new WrappedMethod(main_call);
221
+        const next_methods = arounds.slice(1).concat([wrapped_main_call]);
222
+        return apply_method(arounds[0], args, next_methods);
223
+    }
224
+}
225
+
226
+function apply_method(method, args, next_methods) {
227
+    const method_context = {
228
+        call_next_method(...cnm_args) {
229
+            if (next_methods.length === 0) {
230
+                throw new Error(`no next method for genfun ${method.generic_function.name}`);
231
+            }
232
+
233
+            return method instanceof WrappedMethod
234
+                ? method.continuation()
235
+                : apply_methods(method.generic_function, cnm_args.length > 0 ? cnm_args : args, next_methods);
236
+        },
237
+        get next_method_p() {
238
+            return next_methods.length === 0
239
+        }
240
+    };
241
+
242
+    return method.body ? method.body.bind(method_context)(...args) : method.continuation();
243
+}
0 244
new file mode 100644
... ...
@@ -0,0 +1,234 @@
1
+const before_qualifier = Symbol.for('before');
2
+const after_qualifier = Symbol.for('after');
3
+const around_qualifier = Symbol.for('around');
4
+
5
+let genfun_prototype = {
6
+    name: "(placeholder)",
7
+    lambda_list: [],
8
+    methods: [],
9
+    method(qualifiers, specializers, body) {
10
+        ensure_method(this, this.lambda_list, qualifiers, specializers, body)
11
+        return this;
12
+    },
13
+    primary(specializers, body) {
14
+        return this.method([], specializers, body);
15
+    },
16
+    before(specializers, body) {
17
+        return this.method([before_qualifier], specializers, body);
18
+    },
19
+    after(specializers, body) {
20
+        return this.method([after_qualifier], specializers, body);
21
+    },
22
+    around(specializers, body) {
23
+        return this.method([around_qualifier], specializers, body);
24
+    },
25
+    get fn() {
26
+        const gf = this;
27
+        const lambda = (function() {
28
+            return apply_generic_function(gf, [].slice.call(arguments));
29
+        }).bind(gf);
30
+        return Object.defineProperties(lambda, {
31
+            'name': {value: gf.name},
32
+            'lambda_list': {value: gf.lambda_list},
33
+            'gf': {value: gf},
34
+        });
35
+    }
36
+};
37
+
38
+function GenericFunction(name, lambda_list) {
39
+    if (! (this instanceof GenericFunction) ) {
40
+        return new GenericFunction(...arguments);
41
+    }
42
+
43
+    this.name = name;
44
+    this.lambda_list = lambda_list;
45
+    this.methods = [];
46
+}
47
+
48
+GenericFunction.prototype = Object.create(genfun_prototype);
49
+
50
+let method_prototype = {
51
+    lambda_list: [],
52
+    qualifiers: [],
53
+    specializrs: [],
54
+    body() { throw new Error('Unimplemented'); },
55
+    environment: {},
56
+    generic_function: {},
57
+};
58
+
59
+function StandardMethod(
60
+    lambda_list, qualifiers, specializers, body
61
+) {
62
+    if (! (this instanceof StandardMethod) ) {
63
+        return new StandardMethod(...arguments);
64
+    }
65
+
66
+    this.lambda_list = lambda_list;
67
+    this.qualifiers = qualifiers;
68
+    this.specializers = specializers;
69
+    this.body = body;
70
+    this.generic_function = null;
71
+}
72
+
73
+function ensure_method(gf, lambda_list, qualifiers, specializers, body) {
74
+    let new_method = StandardMethod(...[].slice.call(arguments, 1));
75
+    add_method(gf, new_method);
76
+    return new_method;
77
+}
78
+
79
+function add_method(gf, method) {
80
+    method.generic_function = gf;
81
+    gf.methods.push(method);
82
+    return method;
83
+}
84
+
85
+function classes_of(args) {
86
+    return args.map(Object.getPrototypeOf);
87
+}
88
+
89
+const required_portion = x => x;
90
+
91
+function apply_generic_function(gf, args) {
92
+    let applicable_methods =
93
+        compute_applicable_methods_using_classes(gf, required_portion(args));
94
+    if (applicable_methods.length === 0) {
95
+        throw new Error(`no applicable methods for gf ${gf.name} with args ${JSON.stringify(args)}`);
96
+    } else {
97
+        return apply_methods(gf, args, applicable_methods);
98
+    }
99
+}
100
+
101
+function method_more_specific_p(m1, m2, required_classes) {
102
+    const m1specializers = m1.specializers;
103
+    const m2specializers = m2.specializers;
104
+
105
+    for ([spec1, spec2] of m1specializers.map((el, idx) => [el, m2specializers[idx]])) {
106
+        if (spec1 !== spec2) {
107
+            return sub_specializer_p(spec1, spec2);
108
+        }
109
+    }
110
+}
111
+
112
+function sub_specializer_p(c1, c2) {
113
+    return c1.isPrototypeOf(c2);
114
+}
115
+
116
+const idS = Symbol.for('id');
117
+Object.prototype[idS] = function () { return this };
118
+
119
+export function matchesSpecializer(obj, specializer) {
120
+    let result = obj === specializer.prototype;
121
+    let objType = typeof obj;
122
+
123
+    if (!result && objType === 'object') {
124
+        result = Object.isPrototypeOf.call(specializer.prototype, obj);
125
+    } else if (objType === 'number') {
126
+        result = matchesSpecializer(Number.prototype, specializer) || matchesSpecializer(specializer.prototype, Number);
127
+    } else if (objType === 'string') {
128
+        result = matchesSpecializer(String.prototype, specializer) || matchesSpecializer(specializer.prototype, String);
129
+    }
130
+
131
+    return result;
132
+}
133
+
134
+
135
+function compute_applicable_methods_using_classes(gf, required_classes) {
136
+    const applicable_methods = gf.methods.filter(
137
+        method => method.specializers.every((specializer, idx) => matchesSpecializer(required_classes[idx], specializer))
138
+    );
139
+
140
+    applicable_methods.sort((a,b) => {
141
+        if (method_more_specific_p(a,b)) {
142
+            return -1;
143
+        }
144
+        if (method_more_specific_p(b,a)) {
145
+            return 1;
146
+        }
147
+        return 0;
148
+    })
149
+
150
+    return applicable_methods;
151
+}
152
+
153
+
154
+function arr_eq(a1, a2) {
155
+    if (a1.length !== a2.length) {
156
+        return false;
157
+    } else {
158
+        for (let x = 0; x < a1.length; x++) {
159
+            if (a1[x] instanceof Array && a2[x] instanceof Array) {
160
+                if (!arr_eq(a1[x], a2[x])) {
161
+                    return false;
162
+                }
163
+            } else if (a1[x] !== a2[x]) {
164
+                return false;
165
+            }
166
+        }
167
+        return true;
168
+    }
169
+}
170
+
171
+const primary_method_p = method => method instanceof WrappedMethod || method.qualifiers.length === 0;
172
+const before_method_p = method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [before_qualifier]);
173
+const after_method_p = method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [after_qualifier]);
174
+const around_method_p = method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [around_qualifier]);
175
+
176
+function WrappedMethod(continuation) {
177
+    this.continuation = continuation;
178
+}
179
+
180
+function apply_methods(gf, args, applicable_methods) {
181
+    const primaries = applicable_methods.filter(primary_method_p);
182
+    const befores = applicable_methods.filter(before_method_p);
183
+    const arounds = applicable_methods.filter(around_method_p);
184
+    const afters = applicable_methods.filter(after_method_p);
185
+    afters.reverse();
186
+
187
+    const main_call = Object.defineProperty(
188
+        function() {
189
+            if (primaries.length === 0) {
190
+                throw new Error(`No primary method for ${gf.name}`);
191
+            }
192
+
193
+            for (let before of befores) {
194
+                apply_method(before, args, []);
195
+            }
196
+
197
+            try {
198
+                return apply_method(primaries[0], args, primaries.slice(1));
199
+            } finally {
200
+                for (let after of afters) {
201
+                    apply_method(after, args, []);
202
+                }
203
+            }
204
+        },
205
+        'name', {value: `main_call_${gf.name}`}, 
206
+    );
207
+
208
+    if (arounds.length === 0) {
209
+        return main_call();
210
+    } else {
211
+        const wrapped_main_call = new WrappedMethod(main_call);
212
+        const next_methods = arounds.slice(1).concat([wrapped_main_call]);
213
+        return apply_method(arounds[0], args, next_methods);
214
+    }
215
+}
216
+
217
+function apply_method(method, args, next_methods) {
218
+    const method_context = {
219
+        call_next_method(...cnm_args) {
220
+            if (next_methods.length === 0) {
221
+                throw new Error(`no next method for genfun ${method.generic_function.name}`);
222
+            }
223
+
224
+            return method instanceof WrappedMethod
225
+                ? method.continuation()
226
+                : apply_methods(method.generic_function, cnm_args.length > 0 ? cnm_args : args, next_methods);
227
+        },
228
+        get next_method_p() {
229
+            return next_methods.length === 0
230
+        }
231
+    };
232
+
233
+    return method.body ? method.body.bind(method_context)(...args) : method.continuation();
234
+}
0 235
new file mode 100644
... ...
@@ -0,0 +1,100 @@
1
+import * as uut from './genfuns.js';
2
+
3
+describe('matchesSpecializer', () => {
4
+    function AThing() {};
5
+    const an_instance = new AThing();
6
+    
7
+    test('works in expected cases', () => {
8
+        expect(uut.matchesSpecializer(an_instance, AThing)).toBeTruthy();
9
+        expect(uut.matchesSpecializer(an_instance, String)).toBeFalsy();
10
+        expect(uut.matchesSpecializer(an_instance, Object)).toBeTruthy();
11
+
12
+        expect(uut.matchesSpecializer(new String("foobar"), String)).toBeTruthy();
13
+        expect(uut.matchesSpecializer(new String("foobar"), Object)).toBeTruthy();
14
+
15
+        expect(uut.matchesSpecializer(new Number(1), Number)).toBeTruthy();
16
+        expect(uut.matchesSpecializer(new Number(1), Object)).toBeTruthy();
17
+        expect(uut.matchesSpecializer(new Number(1), String)).toBeFalsy();
18
+
19
+        expect(uut.matchesSpecializer([], Array)).toBeTruthy();
20
+        expect(uut.matchesSpecializer([], Object)).toBeTruthy();
21
+        expect(uut.matchesSpecializer([], Number)).toBeFalsy();
22
+
23
+        function Foo() {}
24
+        Foo.prototype = Object.create(null);
25
+        const inst = new Foo();
26
+        expect(uut.matchesSpecializer(inst, Foo)).toBeTruthy();
27
+        expect(uut.matchesSpecializer(inst, Object)).toBeFalsy();
28
+    });
29
+
30
+    test('works in for primitives', () => {
31
+        expect(uut.matchesSpecializer(1, Number)).toBeTruthy();
32
+        expect(uut.matchesSpecializer(1, Object)).toBeTruthy();
33
+        expect(uut.matchesSpecializer(1, String)).toBeFalsy();
34
+
35
+        expect(uut.matchesSpecializer("1", String)).toBeTruthy();
36
+        expect(uut.matchesSpecializer("1", Object)).toBeTruthy();
37
+        expect(uut.matchesSpecializer("1", Number)).toBeFalsy();
38
+
39
+        expect(uut.matchesSpecializer(null, Number)).toBeFalsy();
40
+        expect(uut.matchesSpecializer(null, String)).toBeFalsy();
41
+        expect(uut.matchesSpecializer(null, Object)).toBeFalsy();
42
+
43
+        expect(uut.matchesSpecializer(undefined, Number)).toBeFalsy();
44
+        expect(uut.matchesSpecializer(undefined, String)).toBeFalsy();
45
+        expect(uut.matchesSpecializer(undefined, Object)).toBeFalsy();
46
+    });
47
+});
48
+
49
+describe('defgeneric', () => {
50
+    test('methods get called appropriately', () => {
51
+        expect(
52
+            uut.defgeneric("testing1", "a", "b")
53
+                .primary([Object, Object], (_, __) => 1)
54
+                .fn(1,2)
55
+        ).toEqual(1);
56
+
57
+        expect(
58
+            uut.defgeneric("testing1", "a", "b")
59
+                .primary([Number, Number], (_, __) => 1)
60
+                .fn(1,2)
61
+        ).toEqual(1);
62
+
63
+        expect(
64
+            uut.defgeneric("testing1", "a", "b")
65
+                .primary([Number, Number], (_, __) => 2)
66
+                .primary([String, String], (_, __) => 1)
67
+                .fn("1","2")
68
+        ).toEqual(1);
69
+
70
+        let firstCounts = 0;
71
+        expect(
72
+            uut.defgeneric("testing1", "a", "b")
73
+                .primary([Number, Number], (_, __) => firstCounts += 1)
74
+                .primary([String, String], (_, __) => firstCounts += 1)
75
+                .fn("1","2")
76
+        ).toEqual(1);
77
+        expect(firstCounts).toEqual(1);
78
+
79
+        let secondCounts = 0;
80
+        expect(
81
+            uut.defgeneric("testing1", "a", "b")
82
+                .primary([Object, Object], (_, __) => secondCounts += 1)
83
+                .primary([String, String], (_, __) => secondCounts += 1)
84
+                .fn("1","2")
85
+        ).toEqual(1);
86
+        expect(secondCounts).toEqual(1);
87
+
88
+        let thirdCounts = 0;
89
+        expect(
90
+            uut.defgeneric("testing1", "a", "b")
91
+                .before([Object, Object], (_, __) => thirdCounts += 1)
92
+                .primary([String, String], (_, __) => 'hi')
93
+                .after([Object, String], (_, __) => thirdCounts += 1)
94
+                .fn("1","2")
95
+        ).toEqual('hi');
96
+        expect(thirdCounts).toEqual(2);
97
+
98
+
99
+    });
100
+});
0 101
new file mode 100644
... ...
@@ -0,0 +1,47 @@
1
+import * as uut from './genfuns.js';
2
+
3
+describe('matchesSpecializer', () => {
4
+    function AThing() {};
5
+    const an_instance = new AThing();
6
+    
7
+    test('works in expected cases', () => {
8
+        expect(uut.matchesSpecializer(an_instance, AThing)).toBeTruthy();
9
+        expect(uut.matchesSpecializer(an_instance, String)).toBeFalsy();
10
+        expect(uut.matchesSpecializer(an_instance, Object)).toBeTruthy();
11
+
12
+        expect(uut.matchesSpecializer(new String("foobar"), String)).toBeTruthy();
13
+        expect(uut.matchesSpecializer(new String("foobar"), Object)).toBeTruthy();
14
+
15
+        expect(uut.matchesSpecializer(new Number(1), Number)).toBeTruthy();
16
+        expect(uut.matchesSpecializer(new Number(1), Object)).toBeTruthy();
17
+        expect(uut.matchesSpecializer(new Number(1), String)).toBeFalsy();
18
+
19
+        expect(uut.matchesSpecializer([], Array)).toBeTruthy();
20
+        expect(uut.matchesSpecializer([], Object)).toBeTruthy();
21
+        expect(uut.matchesSpecializer([], Number)).toBeFalsy();
22
+
23
+        function Foo() {}
24
+        Foo.prototype = Object.create(null);
25
+        const inst = new Foo();
26
+        expect(uut.matchesSpecializer(inst, Foo)).toBeTruthy();
27
+        expect(uut.matchesSpecializer(inst, Object)).toBeFalsy();
28
+    });
29
+
30
+    test('works in for primitives', () => {
31
+        expect(uut.matchesSpecializer(1, Number)).toBeTruthy();
32
+        expect(uut.matchesSpecializer(1, Object)).toBeTruthy();
33
+        expect(uut.matchesSpecializer(1, String)).toBeFalsy();
34
+
35
+        expect(uut.matchesSpecializer("1", String)).toBeTruthy();
36
+        expect(uut.matchesSpecializer("1", Object)).toBeTruthy();
37
+        expect(uut.matchesSpecializer("1", Number)).toBeFalsy();
38
+
39
+        expect(uut.matchesSpecializer(null, Number)).toBeFalsy();
40
+        expect(uut.matchesSpecializer(null, String)).toBeFalsy();
41
+        expect(uut.matchesSpecializer(null, Object)).toBeFalsy();
42
+
43
+        expect(uut.matchesSpecializer(undefined, Number)).toBeFalsy();
44
+        expect(uut.matchesSpecializer(undefined, String)).toBeFalsy();
45
+        expect(uut.matchesSpecializer(undefined, Object)).toBeFalsy();
46
+    });
47
+})
0 48
new file mode 100644
... ...
@@ -0,0 +1,106 @@
1
+function zipWith(fn, ...args) {
2
+    const minLen = Math.min(...args.map(x => x.length));
3
+    const res = [];
4
+
5
+    for (let x = 0; x < minLen; x++) {
6
+        res.push(fn(...args.map(a => a[x])));
7
+    }
8
+
9
+    return res;
10
+}
11
+
12
+const gf = GenericFunction(
13
+    "foobar", ["a", "b"]
14
+).before(
15
+    [Object, Array], function (a,b) {console.log('in before', this.next_method_p);} 
16
+).primary(
17
+    [Object, Array], function (a,b) {
18
+        console.info('next_result: ', this.call_next_method(), this.next_method_p);
19
+        return [a,...b];
20
+    } 
21
+).primary(
22
+    [Object, Object], function (thing, single) {
23
+        console.log("hello from previous method", this.next_method_p);
24
+        return [thing, single];
25
+    }
26
+).after(
27
+    [Number, Array], function (a,b) {console.log(`in after for ${a}`, this.next_method_p);} 
28
+).fn;
29
+
30
+function groupGFMessages(gf) {
31
+    return gf.method([around_qualifier], [Object,Object], function(a,b) {
32
+        console.groupCollapsed(gf.name);
33
+        try {
34
+            return this.call_next_method();
35
+        } finally {
36
+            console.groupEnd();
37
+        }
38
+    })
39
+}
40
+
41
+groupGFMessages(gf.gf);
42
+
43
+console.log(gf(2,["asdf"]));
44
+
45
+const gf2 = GenericFunction(
46
+    "another", ["a"]
47
+).primary(
48
+    [Object], function (a) { return {value: a}; }
49
+).method(
50
+    [around_qualifier], [Number], function(thing) {
51
+        console.log('before next method in number around');
52
+        const val = this.call_next_method();
53
+        console.log('after next method in number around', val);
54
+        return {was_num: true, ...val};
55
+    }
56
+).method(
57
+    [around_qualifier], [Object], function(thing) {
58
+        console.log('before next method in generic around');
59
+        const val = this.call_next_method();
60
+        console.log('after next method in generic around', val);
61
+        return {was_obj: true, ...val};
62
+    }
63
+);
64
+
65
+function MyStore() {
66
+    this.name = 'foo';
67
+    this.address = '1234 asdfadfd'
68
+}
69
+
70
+class NameField extends HTMLElement {constructor() {
71
+    super()
72
+    const style = document.createElement('style');
73
+    this.appendChild(style);
74
+}}
75
+customElements.define('the-name', NameField);
76
+
77
+class AddressField extends HTMLElement {
78
+    constructor() { super() }
79
+}
80
+customElements.define('the-address', AddressField);
81
+
82
+const renderFn = defgeneric(
83
+    "dorender", ["component", "el"]
84
+).primary(
85
+    [MyStore, NameField], function (comp, heading) {
86
+        console.log('heading el', this.next_method_p);
87
+        heading.textContent = comp.name;
88
+    }
89
+).primary(
90
+    [MyStore, AddressField], function (comp, el) {
91
+        console.log('address el', this.next_method_p);
92
+        el.textContent = comp.address;
93
+    }
94
+).primary(
95
+    [MyStore, HTMLElement], function (comp, el) {
96
+        console.log('HtmlElement el ', el, this.next_method_p);
97
+        renderFn.fn(comp, el.querySelector('the-name'));
98
+        renderFn.fn(comp, el.querySelector('the-address'));
99
+    }
100
+).before(
101
+    [Object, HTMLElement], function (_, el) {
102
+        console.log('has next? ', this.next_method_p)
103
+    }
104
+);
105
+
106
+renderFn.fn(new MyStore(), document.querySelector('section'));
0 107
new file mode 100644
... ...
@@ -0,0 +1,88 @@
1
+function zipWith(fn, ...args) {
2
+    const minLen = Math.min(...args.map(x => x.length));
3
+    const res = [];
4
+
5
+    for (let x = 0; x < minLen; x++) {
6
+        res.push(fn(...args.map(a => a[x])));
7
+    }
8
+
9
+    return res;
10
+}
11
+
12
+const gf = GenericFunction(
13
+    "foobar", ["a", "b"]
14
+).before(
15
+    [Object, Array], function (a,b) {console.log('in before', this.next_method_p);} 
16
+).primary(
17
+    [Object, Array], function (a,b) {
18
+        console.info('next_result: ', this.call_next_method(), this.next_method_p);
19
+        return [a,...b];
20
+    } 
21
+).primary(
22
+    [Object, Object], function (thing, single) {
23
+        console.log("hello from previous method", this.next_method_p);
24
+        return [thing, single];
25
+    }
26
+).after(
27
+    [Number, Array], function (a,b) {console.log(`in after for ${a}`, this.next_method_p);} 
28
+).fn;
29
+
30
+function groupGFMessages(gf) {
31
+    return gf.method([around_qualifier], [Object,Object], function(a,b) {
32
+        console.groupCollapsed(gf.name);
33
+        try {
34
+            return this.call_next_method();
35
+        } finally {
36
+            console.groupEnd();
37
+        }
38
+    })
39
+}
40
+
41
+groupGFMessages(gf.gf);
42
+
43
+console.log(gf(2,["asdf"]));
44
+
45
+const gf2 = GenericFunction(
46
+    "another", ["a"]
47
+).primary(
48
+    [Object], function (a) { return {value: a}; }
49
+).method(
50
+    [around_qualifier], [Number], function(thing) {
51
+        console.log('before next method in number around');
52
+        const val = this.call_next_method();
53
+        console.log('after next method in number around', val);
54
+        return {was_num: true, ...val};
55
+    }
56
+).method(
57
+    [around_qualifier], [Object], function(thing) {
58
+        console.log('before next method in generic around');
59
+        const val = this.call_next_method();
60
+        console.log('after next method in generic around', val);
61
+        return {was_obj: true, ...val};
62
+    }
63
+);
64
+
65
+function MyComponent() {
66
+    this.name = 'foo';
67
+    this.address = '1234 asdfadfd'
68
+}
69
+
70
+const renderFn = GenericFunction(
71
+    "dorender", ["component", "el"]
72
+).primary(
73
+    [MyComponent, HTMLHeadingElement], (comp, heading) => {
74
+        console.log('heading el');
75
+        heading.textContent = comp.name;
76
+    }
77
+).primary(
78
+    [MyComponent, HTMLDivElement], (comp, el) => {
79
+        console.log('address el');
80
+        el.textContent = comp.address;
81
+    }
82
+).primary(
83
+    [MyComponent, HTMLElement], (comp, el) => {
84
+        console.log('HtmlElement el ', el);
85
+        renderFn.fn(comp, el.querySelector('h2'));
86
+        renderFn.fn(comp, el.querySelector('div'));
87
+    }
88
+)