Search code examples
lispcommon-lisptacit-programming

Tacit programming in Lisp


Is it possible to use/implement tacit programming (also known as point-free programming) in Lisp? And in case the answer is yes, has it been done?


Solution

  • This style of programming is possible in CL in principle, but, being a Lisp-2, one has to add several #'s and funcalls. Also, in contrast to Haskell for example, functions are not curried in CL, and there is no implicit partial application. In general, I think that such a style would not be very idiomatic CL.

    For example, you could define partial application and composition like this:

    (defun partial (function &rest args)
      (lambda (&rest args2) (apply function (append args args2))))
    
    (defun comp (&rest functions)
      (flet ((step (f g) (lambda (x) (funcall f (funcall g x)))))
        (reduce #'step functions :initial-value #'identity)))
    

    (Those are just quick examples I whipped up – they are not really tested or well thought-through for different use-cases.)

    With those, something like map ((*2) . (+1)) xs in Haskell becomes:

    CL-USER> (mapcar (comp (partial #'* 2) #'1+) '(1 2 3))
    (4 6 8)
    

    The sum example:

    CL-USER> (defparameter *sum* (partial #'reduce #'+))
    *SUM*
    CL-USER> (funcall *sum* '(1 2 3))
    6
    

    (In this example, you could also set the function cell of a symbol instead of storing the function in the value cell, in order to get around the funcall.)

    In Emacs Lisp, by the way, partial application is built-in as apply-partially.

    In Qi/Shen, functions are curried, and implicit partial application (when functions are called with one argument) is supported:

    (41-) (define comp F G -> (/. X (F (G X))))
    comp
    
    (42-) ((comp (* 2) (+ 1)) 1)
    4
    
    (43-) (map (comp (* 2) (+ 1)) [1 2 3])
    [4 6 8]
    

    There is also syntactic threading sugar in Clojure that gives a similar feeling of "pipelining":

    user=> (-> 0 inc (* 2))
    2