-
Notifications
You must be signed in to change notification settings - Fork 222
Expand file tree
/
Copy pathmulti-methods.factor
More file actions
280 lines (214 loc) · 7.39 KB
/
multi-methods.factor
File metadata and controls
280 lines (214 loc) · 7.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
! Copyright (C) 2008, 2009 Slava Pestov.
! See https://factorcode.org/license.txt for BSD license.
USING: accessors arrays assocs classes classes.algebra
combinators debugger definitions effects effects.parser io
kernel make math math.order namespaces parser prettyprint
prettyprint.backend prettyprint.custom quotations see sequences
sequences.generalizations sets sorting vectors words ;
FROM: namespaces => set ;
QUALIFIED: syntax
IN: multi-methods
! PART I: Converting hook specializers
: canonicalize-specializer-0 ( specializer -- specializer' )
[ \ f or ] map ;
SYMBOL: args
SYMBOL: hooks
SYMBOL: total
: canonicalize-specializer-1 ( specializer -- specializer' )
[
[ class? ] filter
[ length <iota> <reversed> [ 1 + neg ] map ] keep zip
[ length args [ max ] change ] keep
]
[
[ pair? ] filter
[ keys [ hooks get adjoin ] each ] keep
] bi append ;
: canonicalize-specializer-2 ( specializer -- specializer' )
[
[
{
{ [ dup integer? ] [ ] }
{ [ dup word? ] [ hooks get index ] }
} cond args get +
] dip
] assoc-map ;
: canonicalize-specializer-3 ( specializer -- specializer' )
[ total get object <array> <enumerated> ] dip assoc-union! seq>> ;
: canonicalize-specializers ( methods -- methods' hooks )
[
[ [ canonicalize-specializer-0 ] dip ] assoc-map
0 args set
V{ } clone hooks set
[ [ canonicalize-specializer-1 ] dip ] assoc-map
hooks [ sort ] change
[ [ canonicalize-specializer-2 ] dip ] assoc-map
args get hooks get length + total set
[ [ canonicalize-specializer-3 ] dip ] assoc-map
hooks get
] with-scope ;
: drop-n-quot ( n -- quot ) \ drop <repetition> >quotation ;
: prepare-method ( method n -- quot )
[ 1quotation ] [ drop-n-quot ] bi* prepend ;
: prepare-methods ( methods -- methods' prologue )
canonicalize-specializers
[ length [ prepare-method ] curry assoc-map ] keep
[ [ get ] curry ] map [ ] concat-as ;
! Part II: Topologically sorting specializers
: maximal-element ( seq quot -- n elt )
dupd [
swapd [ call +lt+ = ] 2curry none?
] 2curry find [ "Topological sort failed" throw ] unless* ;
inline
: topological-sort ( seq quot -- newseq )
[ >vector [ dup empty? not ] ] dip
[ dupd maximal-element [ over remove-nth! drop ] dip ] curry
produce nip ; inline
: classes< ( seq1 seq2 -- lt/eq/gt )
[
{
{ [ 2dup eq? ] [ +eq+ ] }
{ [ 2dup [ class<= ] [ swap class<= ] 2bi and ] [ +eq+ ] }
{ [ 2dup class<= ] [ +lt+ ] }
{ [ 2dup swap class<= ] [ +gt+ ] }
[ +eq+ ]
} cond 2nip
] 2map [ +eq+ eq? not ] find nip +eq+ or ;
: sort-methods ( alist -- alist' )
[ [ first ] bi@ classes< ] topological-sort ;
! PART III: Creating dispatch quotation
: picker ( n -- quot )
{
{ 0 [ [ dup ] ] }
{ 1 [ [ over ] ] }
{ 2 [ [ pick ] ] }
[ 1 - picker [ dip swap ] curry ]
} case ;
: (multi-predicate) ( class picker -- quot )
swap predicate-def append ;
: multi-predicate ( classes -- quot )
dup length <iota> <reversed>
[ picker 2array ] 2map
[ object eq? ] reject-keys
[ [ t ] ] [
[ (multi-predicate) ] { } assoc>map
unclip [ swap [ f ] \ if 3array [ ] append-as ] reduce
] if-empty ;
: argument-count ( methods -- n )
keys 0 [ length max ] reduce ;
ERROR: no-method arguments generic ;
: make-default-method ( methods generic -- quot )
[ argument-count ] dip [ [ narray ] dip no-method ] 2curry ;
: multi-dispatch-quot ( methods generic -- quot )
[ make-default-method ]
[ drop [ [ multi-predicate ] dip ] assoc-map reverse! ]
2bi alist>quot ;
! Generic words
PREDICATE: generic < word
"multi-methods" word-prop >boolean ;
: methods ( word -- alist )
"multi-methods" word-prop >alist ;
: make-generic ( generic -- quot )
[
[ methods prepare-methods % sort-methods ] keep
multi-dispatch-quot %
] [ ] make ;
: update-generic ( word -- )
dup make-generic define ;
! Methods
PREDICATE: method-body < word
"multi-method-generic" word-prop >boolean ;
M: method-body stack-effect
"multi-method-generic" word-prop stack-effect ;
M: method-body crossref?
"forgotten" word-prop not ;
: method-word-name ( specializer generic -- string )
[ name>> % "-" % unparse % ] "" make ;
: method-word-props ( specializer generic -- assoc )
[
"multi-method-generic" ,,
"multi-method-specializer" ,,
] H{ } make ;
: <method> ( specializer generic -- word )
[ method-word-props ] 2keep
method-word-name f <word>
swap >>props ;
: with-methods ( word quot -- )
over [
[ "multi-methods" word-prop ] dip call
] dip update-generic ; inline
: reveal-method ( method classes generic -- )
[ set-at ] with-methods ;
: method ( classes word -- method )
"multi-methods" word-prop at ;
: create-method ( classes generic -- method )
2dup method dup [
2nip
] [
drop [ <method> dup ] 2keep reveal-method
] if ;
: niceify-method ( seq -- seq )
[ dup \ f eq? [ drop f ] when ] map ;
M: no-method error.
"Type check error" print
nl
"Generic word " write dup generic>> pprint
" does not have a method applicable to inputs:" print
dup arguments>> short.
nl
"Inputs have signature:" print
dup arguments>> [ class-of ] map niceify-method .
nl
"Available methods: " print
generic>> methods canonicalize-specializers drop sort-methods
keys [ niceify-method ] map stack. ;
: forget-method ( specializer generic -- )
[ delete-at ] with-methods ;
: method>spec ( method -- spec )
[ "multi-method-specializer" word-prop ]
[ "multi-method-generic" word-prop ] bi prefix ;
: define-generic ( word effect -- )
[ set-stack-effect ] keepd
dup "multi-methods" word-prop [ drop ] [
[ H{ } clone "multi-methods" set-word-prop ]
[ update-generic ]
bi
] if ;
! Syntax
SYNTAX: GENERIC: scan-new-word scan-effect define-generic ;
: parse-method ( -- quot classes generic )
parse-definition [ 2 tail ] [ second ] [ first ] tri ;
: create-method-in ( specializer generic -- method )
create-method dup save-location f set-last-word ;
: scan-new-method ( -- method )
scan-word scan-object swap create-method-in ;
: (METHOD:) ( -- method def ) scan-new-method parse-definition ;
SYNTAX: METHOD: (METHOD:) define ;
! For compatibility
SYNTAX: M:
scan-word 1array scan-word create-method-in
parse-definition
define ;
! Definition protocol. We qualify core generics here
syntax:M: generic definer drop \ GENERIC: f ;
syntax:M: generic definition drop f ;
PREDICATE: method-spec < array
unclip generic? [ [ class? ] all? ] dip and ;
syntax:M: method-spec where
dup unclip method or? [ first ] unless where ;
syntax:M: method-spec set-where
unclip method set-where ;
syntax:M: method-spec definer
unclip method definer ;
syntax:M: method-spec definition
unclip method definition ;
syntax:M: method-spec synopsis*
unclip method synopsis* ;
syntax:M: method-spec forget*
unclip method forget* ;
syntax:M: method-body definer
drop \ METHOD: \ ; ;
syntax:M: method-body synopsis*
dup definer.
[ "multi-method-generic" word-prop pprint-word ]
[ "multi-method-specializer" word-prop pprint* ] bi ;