| Briefly: A hack (rntz.coerce.0 on anarki) which does the following: (coerce "foo" 'fn) gives you back a fn that does what "foo" does in functional position; this is internally used to handle non-fn things in functional position; and coercion is modifiable from arc, so you can modify this in arc. This allows you to define how to call objects of arbitrary types in arc; in particular, rntz.defcall.0 on anarki ports arc2-anarki's defcall to this backend. It occurred to me that since I can use strings, tables, and lists as functions, I should be able to coerce them to 'fns. So I hacked this in: arc> (= foofn (coerce "foo" 'fn))
#<procedure:...e/arc/arc/ac.scm:857:33>
arc> (map foofn (range 0 2))
(#\f #\o #\o)
However, in hacking this in, I though "wouldn't it be cool if coercion were modifiable from arc"? And then I realized that by combining these two ideas - coercion to 'fn, and modifiable coercion - I'd have a recipe for something from Anarki that I had really liked, namely, 'defcall - the ability to define how to call a given type of object. All I needed to do was ensure that calling a non-fn was equivalent to coercing it to a 'fn and calling that.This idea in turn provides a really neat way to deal with the problem that 'ac-call faces: namely, that it defaults to using 'ar-apply, which conses up a list to apply the function (or string or table, etc) to, which causes a lot of time to be spent gc'ing these one-off lists. The solution currently used is to special-case all numbers of arguments up to four using functions 'ar-funcall{0..4}, and then fall back to 'ar-apply for five or more arguments. But if, instead of dealing with the whole thing in one chunk, we just compile the thing in functional position to (ar-coerce <foo> 'fn), we can do away with all of the consing. If we do this, 'ac-call goes from being this monstrosity: (define (ac-call fn args env)
(let ((macfn (ac-macro? fn)))
(cond (macfn
(ac-mac-call macfn args env))
((and (pair? fn) (eqv? (car fn) 'fn))
`(,(ac fn env) ,@(ac-args (cadr fn) args env)))
((and direct-calls (symbol? fn) (not (lex? fn env)) (bound? fn)
(procedure? (namespace-variable-value (ac-global-name fn))))
(ac-global-call fn args env))
((= (length args) 0)
`(ar-funcall0 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args)))
((= (length args) 1)
`(ar-funcall1 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args)))
((= (length args) 2)
`(ar-funcall2 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args)))
((= (length args) 3)
`(ar-funcall3 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args)))
((= (length args) 4)
`(ar-funcall4 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args)))
(#t
`(ar-apply ,(ac fn env)
(list ,@(map (lambda (x) (ac x env)) args)))))))
To this: (define (ac-call fn args env)
(let ((macfn (ac-macro? fn)))
(cond (macfn
(ac-mac-call macfn args env))
((and (pair? fn) (eqv? (car fn) 'fn))
`(,(ac fn env) ,@(ac-args (cadr fn) args env)))
((and direct-calls (symbol? fn) (not (lex? fn env)) (bound? fn)
(procedure? (namespace-variable-value (ac-global-name fn))))
(ac-global-call fn args env))
(#t
`((ar-coerce ,(ac fn env) 'fn)
,@(map (lambda (x) (ac x env)) args))))))
Moreover, we get to eliminate 'ar-funcall{0..4}. So not only do we get the ability to coerce things to 'fns, programmable coercion, and eliminate argument consing; we actually reduce our codebase!The programmable coercion is done by way of making coercion based on a double hashtable lookup. To (coerce 'foo 'string), we first look up 'string in coerce* , and then look up 'sym (the type of 'foo) in the resulting table, and call the procedure we get on 'foo (and any other args passed to coerce, as in (coerce "f" 'int 16) to interpret "f" as a hexadecimal integer). So to modify coercion from arc, you just add conversion functions to the appropriate spots in coerce* . This makes 'defcall easy: (def set-coercer (to from fun)
(let cnv (or coerce*.to (= coerce*.to (table)))
(= cnv.from fun)))
(mac defcoerce (to from parms . body)
`(set-coercer ',to ',from (fn ,parms ,@body)))
(mac defcall (type-name parms . body)
(w/uniq (fnobj args)
`(defcoerce fn ,type-name (,fnobj)
(fn ,args (let ,parms (cons (rep ,fnobj) ,args) ,@body)))))
As mentioned at the top, to grab this hack, grab rntz.coerce.0 from anarki; and to grab 'set-coercer, 'defcoerce, and 'defcall, grab rntz.defcall.0. Both have been merged into arc3.master. |