Hey everyone- I've put together a simple (but comprehensive) implementation of implicit currying. The changes are fully compatible with all existing code. It does have one drawback: It will cut your performance by about 2/3rds. (ouch) BTW, this design was partially inspired by rincewind's ideas in http://arclanguage.org/item?id=7788 The performance issues are due to extra checks required at function call time. Also, Mzscheme 3.x does not have a way to control function arity (4.x does) so I had to use an 'eval in the code in a trick to control arity. This is the only implementation of implicit currying in a dynamically-typed language I have ever seen- Of course, that could just be because people who have thought about it decided it's a bad idea. I, however, disagree: I think it's quite useful in many common programming situations... but you'll want to judge its merits on your own. Here are some examples of this feature in use: arc> (mem 5)
#<procedure>
arc> (= contains-five (mem 5))
#<procedure>
arc> (contains-five '(1 2 3))
nil
arc> (contains-five '(3 4 5))
(5)
arc> (map [mem 5 _] '((1 2 3) (3 4 5)))
(nil (5))
arc> (map (mem 5) '((1 2 3) (3 4 5)))
(nil (5))
arc> (map mem.5 '((1 2 3) (3 4 5)))
(nil (5))
Since many arc commands have a variable arity, this implicit currying is useful only if you also have a way to constrain function arity. My implementation allows you to place a literal number in the function position and use that to constrain the arity when you want to curry a variable-arity function: arc> (= triple-plus (3 +))
#<procedure>
arc> (triple-plus 1 2 3)
6
arc> (triple-plus 1 2)
#<procedure>
arc> ((triple-plus 1 2) 3)
6
arc> (map (2.+ 1) '(100 200 300))
(101 201 301)
As a final example, here I declare a function in full "point free style"- This is commonly used in Haskell. It roughly means that you're defining a function without explicitly referring to any arguments: arc> (= list-add-one (map1 (2.+ 1)))
#<procedure>
arc> (list-add-one '(100 200 300))
(101 201 301)
...You've got to admit that the definition of "list-add-one" is extremely expressive. Note that I used map1 in the command, since it has the required fixed arity already. I could have, instead, used "2.map".That gives you a rough idea of how implicit currying and how arity constraints work. Here are the changes you need to make in ac.scm to implement this feature: (define (genlst n)
(if (zero? n)
()
(cons (gensym) (genlst (- n 1)))))
(define (procedure-reduce-arity fn x)
(let ((gen (genlst x)))
(eval `(lambda ,gen
(,fn ,@gen)))))
(define (ar-apply fn args)
(cond ((procedure? fn) (let ((x (procedure-arity fn)))
(if (not (number? x))
(apply fn args)
(let ((y (length args)))
(if (<= x y)
(apply fn args)
(procedure-reduce-arity (lambda lst
(ar-apply fn (append args lst)))
(- x y)))))))
((pair? fn) (list-ref fn (car args)))
((string? fn) (string-ref fn (car args)))
((hash-table? fn) (ar-nill (hash-table-get fn (car args) #f)))
((number? fn) (procedure-reduce-arity (car args) fn))
(#t (err "Function call on inappropriate object" fn args))))
(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) ,@(map (lambda (x) (ac x env)) args)))
(#t
`(ar-apply ,(ac fn env)
(list ,@(map (lambda (x) (ac x env)) args)))))))
|