在Lisp中是否可能使用/实现tacit programming(也称为point-free programming)?如果是肯定的,是否已经实现了?
原则上,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)))
(这些只是我随便编写的快速示例 - 它们并没有经过不同用例的充分测试或深思熟虑。)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.)(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
->
多做了一些事情):(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))
|>
(以及 shell 管道) 而不是 Haskell 中的 .
,但它们基本上是相同的东西 (我更喜欢 |>
,但这是个人口味问题)。->
的作用,只需将最后一个示例宏展开三次 (在 SLIME 中,这可以通过将光标放在示例的第一个 (
上并键入 C-c RET
三次来完成)。是的,这是可能的,在此@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))))
(curry #'+ 3)
在书中查看更多相关内容。
compose
示例也适用于我的版本。我只是在 comp
中有一个小的复制和粘贴错误或打字错误(在 REPL 中工作版本的 step
函数调用顺序不正确),现在已经修复了。此外,我认为 Rainer 是正确的:虽然在 CL 中能够这样做很好,但它并不是非常惯用的。Haskell 更适合这种编程风格。(顺便说一下,Graham 的风格被许多 CL 程序员认为相当特别)。 - danlei通常情况下,只要使用正确的函数,这是可能的。例如,以下是在Racket中实现维基百科页面上的sum
函数的示例:
#lang racket
(define sum (curry foldr + 0))
由于默认情况下过程不是柯里化的,因此最好使用curry
或以显式柯里化风格编写您的函数。您可以使用新的define
宏抽象出这个过程并使用柯里化。
as->
或->
/->>
与as->
组合:http://clojuredocs.org/clojure.core/as-%3E - Erik Kaplun