Lisp中的隐式编程

19

在Lisp中是否可能使用/实现tacit programming(也称为point-free programming)?如果是肯定的,是否已经实现了?

4个回答

16

原则上,Common Lisp 支持这种编程风格,但由于它是 Lisp-2,因此需要添加几个 #'funcall。与 Haskell 不同的是,在 CL 中函数不是柯里化的,并且没有隐式的部分应用。总的来说,我认为这种风格在 CL 中不是很符合惯用法。

例如,您可以像这样定义部分应用和组合:

(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)))
(这些只是我随便编写的快速示例 - 它们并没有经过不同用例的充分测试或深思熟虑。)
使用这些东西,Haskell 中类似于 map ((*2) . (+1)) xs 的函数变为:
CL-USER> (mapcar (comp (partial #'* 2) #'1+) '(1 2 3))
(4 6 8)

这是一个 sum 的例子:

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.)
在这个例子中,你也可以将符号的函数单元格设置为函数本身,而不是将函数存储在值单元格中,以避免调用函数(funcall)。
In Emacs Lisp, by the way, partial application is built-in as apply-partially.
顺便提一下,在Emacs Lisp中,部分应用内置为apply-partially。
In Qi/Shen, functions are curried, and implicit partial application (when functions are called with one argument) is supported:
在Qi/Shen中,函数是柯里化的,并且支持隐式部分应用(当使用一个参数调用函数时)。
(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]

Clojure还有一种语法线程糖,可以给人一种类似“管道”式的感觉:

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

8
你可以使用类似以下的代码(这比Clojure中的->多做了一些事情):
(defmacro -> (obj &rest forms)
  "Similar to the -> macro from clojure, but with a tweak: if there is
  a $ symbol somewhere in the form, the object is not added as the
  first argument to the form, but instead replaces the $ symbol."
  (if forms
      (if (consp (car forms))
          (let* ((first-form (first forms))
                 (other-forms (rest forms))
                 (pos (position '$ first-form)))
            (if pos
                `(-> ,(append (subseq first-form 0 pos)
                              (list obj)
                              (subseq first-form (1+ pos)))
                     ,@other-forms)
                `(-> ,(list* (first first-form) obj (rest first-form))
                     ,@other-forms)))
          `(-> ,(list (car forms) obj)
               ,@(cdr forms)))
      obj))

在将->放置在名为tacit的包中并将tacit放置在您计划使用->的任何包的use子句中时,您必须小心地从该包中导出符号$,以便继承->$

使用示例:

(-> "TEST"
    string-downcase
    reverse)

(-> "TEST"
    reverse
    (elt $ 1))

这更像是 F# 中的 |> (以及 shell 管道) 而不是 Haskell 中的 .,但它们基本上是相同的东西 (我更喜欢 |>,但这是个人口味问题)。
要查看 -> 的作用,只需将最后一个示例宏展开三次 (在 SLIME 中,这可以通过将光标放在示例的第一个 ( 上并键入 C-c RET 三次来完成)。

从Clojure 1.5开始,您可以使用as->->/->>as->组合:http://clojuredocs.org/clojure.core/as-%3E - Erik Kaplun

3

是的,这是可能的,在此@danlei已经解释得很好。我将从Paul Graham的《ANSI Common Lisp》一书中的第6.6章节添加一些例子来说明函数构建器:

您可以定义一个如下的函数构建器:

(defun compose (&rest fns)
  (destructuring-bind (fn1 . rest) (reverse fns)
    #'(lambda (&rest args)
        (reduce #'(lambda (v f) (funcall f v))
                rest
                :initial-value (apply fn1 args)))))

(defun curry (fn &rest args)
  #'(lambda (&rest args2)
      (apply fn (append args args2))))

并像这样使用它

(mapcar (compose #'list #'round #'sqrt)
        '(4 9 16 25))

返回值

((2) (3) (4) (5))

compose 函数的调用:

(compose #'a #'b #'c)

等同于

#'(lambda (&rest args) (a (b (apply #'c args))))

这意味着compose函数可以接受任意数量的参数。创建一个将3添加到参数中的函数:
(curry #'+ 3)

在书中查看更多相关内容。


2
但这并没有太多意义。它会导致糟糕的代码。更难阅读和调试。此外,几乎每个CL编译器都会为此生成缓慢的代码(consed arg列表等)。 - Rainer Joswig
@RainerJoswig 您说得对。这可以展示Common Lisp有多么灵活,并且闭包可以帮助我们... o_O - Juanito Fatas
2
你的 compose 示例也适用于我的版本。我只是在 comp 中有一个小的复制和粘贴错误或打字错误(在 REPL 中工作版本的 step 函数调用顺序不正确),现在已经修复了。此外,我认为 Rainer 是正确的:虽然在 CL 中能够这样做很好,但它并不是非常惯用的。Haskell 更适合这种编程风格。(顺便说一下,Graham 的风格被许多 CL 程序员认为相当特别)。 - danlei

2

通常情况下,只要使用正确的函数,这是可能的。例如,以下是在Racket中实现维基百科页面上的sum函数的示例:

#lang racket
(define sum (curry foldr + 0))

由于默认情况下过程不是柯里化的,因此最好使用curry或以显式柯里化风格编写您的函数。您可以使用新的define宏抽象出这个过程并使用柯里化。


2
这个问题涉及到Common Lisp,这个答案对于Scheme是正确的,但不适用于CL。 - Óscar López
2
实际上,问题是关于Lisp的一般性质的。我添加了CL作为标签,因为这是我最熟悉的Lisp方言,但是使用Scheme的答案也很有用。 - paldepind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接