Is it possible to implement auto-currying to the Lisp-family languages?

前端 未结 6 2009
夕颜
夕颜 2020-12-31 06:15

That is, when you call a function with >1 arity with only one argument, it should, instead of displaying an error, curry that argument and return the resulting function with

相关标签:
6条回答
  • 2020-12-31 06:37

    Sure, you just have to decide exact semantics for your language, and then implement your own loader which will translate your source files into the implementation language.

    You could e.g. translate every user function call (f a b c ... z) into (...(((f a) b) c)... z), and every (define (f a b c ... z) ...) to (define f (lambda(a) (lambda(b) (lambda(c) (... (lambda(z) ...) ...))))) on top of a Scheme, to have an auto-currying Scheme (that would forbid varargs functions of course).

    You will also need to define your own primitives, turning the varargs functions like e.g. (+) to binary, and turning their applications to using fold e.g. (+ 1 2 3 4) ==> (fold (+) (list 1 2 3 4) 0) or something - or perhaps just making such calls as (+ 1 2 3 4) illegal in your new language, expecting of its user to write fold forms by themselves.

    That's what I meant by "deciding ... semantics for your language".

    The loader can be as simple as wrapping the file contents into a call to a macro - which you would then have to implement, as per your question.

    0 讨论(0)
  • 2020-12-31 06:43

    The short answer is yes, though not easily.

    you could implament this as a macro that wrapped every call in partial, though only in limited context. Clojure has some features that would make this rather difficult such as variable arity functions and dynamit calls. Clojure lacks a formal type system to concretely decide when the call can have no more arguments and should actually be called.

    0 讨论(0)
  • 2020-12-31 06:48

    Lisp already has Functional Currying:

    * (defun adder (n)
        (lambda (x) (+ x n)))
    ADDER
    

    http://cl-cookbook.sourceforge.net/functions.html

    Here's what I was reading about Lisp macros: https://web.archive.org/web/20060109115926/http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html

    It's possible to implement this in pure Lisp. It's possible to implement it using macros as well, however it seems as though macros would make it more confusing for very basic stuff.

    0 讨论(0)
  • 2020-12-31 06:49

    It's possible, but not easy if you want a useful result.

    • If you want a language that always does simple currying, then the implementation is easy. You just convert every application of more than one input to a nested application, and the same for functions of more than one argument. With Racket's language facilities, this is a very simple exercise. (In other lisps you can get a similar effect by some macro around the code where you want to use it.)

      (Incidentally, I have a language on top of Racket that does just this. It gets the full cuteness of auto-curried languages, but it's not intended to be practical.)

      However, it's not too useful since it only works for functions of one argument. You could make it useful with some hacking, for example, treat the rest of the lisp system around your language as a foreign language and provide forms to use it. Another alternative is to provide your language with arity information about the surrounding lisp's functions. Either of these require much more work.

    • Another option is to just check every application. In other words, you turn every

      (f x y z)
      

      into code that checks the arity of f and will create a closure if there are not enough arguments. This is not too hard in itself, but it will lead to a significant overhead price. You could try to use a similar trick of some information about arities of functions that you'd use in the macro level to know where such closures should be created -- but that's difficult in essentially the same way.

    But there is a much more serious problem, at the highlevel of what you want to do. The thing is that variable-arity functions just don't play well with automatic currying. For example, take an expression like:

    (+ 1 2 3)

    How would you decide if this should be called as is, or whether it should be translated to ((+ 1 2) 3)? It seems like there's an easy answer here, but what about this? (translate to your favorite lisp dialect)

    (define foo (lambda xs (lambda ys (list xs ys))))
    

    In this case you can split a (foo 1 2 3) in a number of ways. Yet another issue is what do you do with something like:

    (list +)
    

    Here you have + as an expression, but you could decide that this is the same as applying it on zero inputs which fits +s arity, but then how do you write an expression that evaluates to the addition function? (Sidenote: ML and Haskell "solves" this by not having nullary functions...)

    Some of these issues can be resolved by deciding that each "real" application must have parens for it, so a + by itself will never be applied. But that loses much of the cuteness of having an auto-curried language, and you still have problems to solve...

    0 讨论(0)
  • 2020-12-31 06:54

    In Scheme it's possible to curry a function using the curry procedure:

    (define (add x y)
      (+ x y))
    
    (add 1 2)           ; non-curried procedure call
    (curry add)         ; curried procedure, expects two arguments
    ((curry add) 1)     ; curried procedure, expects one argument
    (((curry add) 1) 2) ; curried procedure call
    

    From Racket's documentation:

    [curry] returns a procedure that is a curried version of proc. When the resulting procedure is first applied, unless it is given the maximum number of arguments that it can accept, the result is a procedure to accept additional arguments.

    You could easily implement a macro which automatically uses curry when defining new procedures, something like this:

    (define-syntax define-curried
        (syntax-rules ()
          ((_ (f . a) body ...)
           (define f (curry (lambda a (begin body ...)))))))
    

    Now the following definition of add will be curried:

    (define-curried (add a b)
      (+ a b))
    
    add
    > #<procedure:curried>
    
    (add 1)
    > #<procedure:curried>
    
    ((add 1) 2)
    > 3
    
    (add 1 2)
    > 3
    
    0 讨论(0)
  • 2020-12-31 06:57

    As noted by Alex W, the Common Lisp Cookbook does give an example of a "curry" function for Common Lisp. The specific example is further down on that page:

    (declaim (ftype (function (function &rest t) function) curry)
             (inline curry)) ;; optional
    (defun curry (function &rest args)
      (lambda (&rest more-args)
        (apply function (append args more-args))))
    

    Auto-currying shouldn't be that hard to implement, so I took a crack at it. Note that the following isn't extensively tested, and doesn't check that there aren't too many args (the function just completes when there are that number or more):

    (defun auto-curry (function num-args)
      (lambda (&rest args)
        (if (>= (length args) num-args)
            (apply function args)
            (auto-curry (apply (curry #'curry function) args)
                        (- num-args (length args))))))
    

    Seems to work, though:

    * (auto-curry #'+ 3)
    #<CLOSURE (LAMBDA (&REST ARGS)) {1002F78EB9}>
    
    * (funcall (auto-curry #'+ 3) 1)
    #<CLOSURE (LAMBDA (&REST ARGS)) {1002F7A689}>
    
    * (funcall (funcall (funcall (auto-curry #'+ 3) 1) 2) 5)
    8
    
    * (funcall (funcall (auto-curry #'+ 3) 3 4) 7)
    14
    

    A primitive (doesn't handle full lambda lists properly, just simple parameter lists) version of some macro syntax sugar over the above:

    (defmacro defun-auto-curry (fn-name (&rest args) &body body)
      (let ((currying-args (gensym)))
        `(defun ,fn-name (&rest ,currying-args)
           (apply (auto-curry (lambda (,@args) ,@body)
                              ,(length args))
                  ,currying-args))))
    

    Seems to work, though the need for funcall is still annoying:

    * (defun-auto-curry auto-curry-+ (x y z)
        (+ x y z))
    AUTO-CURRY-+
    
    * (funcall (auto-curry-+ 1) 2 3)
    6
    
    * (auto-curry-+ 1)
    #<CLOSURE (LAMBDA (&REST ARGS)) {1002B0DE29}>
    
    0 讨论(0)
提交回复
热议问题