9

在 Haskell 中,如果我有一个如下所示的 lambda

(\x -> doStuff x y)

来自周围y的范围,我可以将它分割并变成

(`doStuff` y)

它更短更简洁(也是我最喜欢 Haskell 的事情之一)。

现在,在 Common Lisp 中,我会将等效代码编写为

(lambda (x) (do-stuff x y))

这对我来说实际上是一件很常见的事情,但我觉得即使是一点点样板也让我有些困扰,所以我想知道是否有办法在 Common Lisp 中获得类似 Haskell 风格的部分?

4

7 回答 7

11

除非您更有经验,否则我建议您学习在 Lisp 中编写 Lisp,而不是如何在 Lisp 中编写 Haskell。后者不是一个好主意。Haskell 的工作方式非常不同。

Lisp 不做任何“currying”(或 schönfinkeling ;-))。

你可以把它写成:

CL-USER 5 > (defun curry (fn arg) (lambda (&rest args) (apply fn arg args))) 
CURRY

CL-USER 6 > (mapcar (curry #'expt 2) '(2 3 4 5 6))
(4 8 16 32 64)

不过,这样会花费一点效率。

CL-USER 7 > (mapcar (lambda (base) (expt base 2)) '(2 3 4 5 6))
(4 8 16 32 64)

我个人更喜欢后者,因为我有一个真正可读的变量名称。这有助于调试器,在那里我看到了回溯。像这样的工具在 Lisp 中可能比在 Haskell 中更重要。

CL-USER 12 > (mapcar (lambda (base) (expt base 2)) '(2 3 "four" 5 6))

错误。让我们看看回溯:

CL-USER 12 : 1 > :bb
...

Condition: In EXPT of ("four" 2) arguments should be of type NUMBER.

Call to SYSTEM::ARGS-TO-BINARY-ARITHMETIC-FN-NOT-OF-TYPE {offset 189}
  SYSTEM::FN-NAME : EXPT
  SYSTEM::ARG1    : "four"
  SYSTEM::ARG2    : 2
  TYPE  {Closing} : NUMBER

Interpreted call to (SUBFUNCTION :ANONYMOUS SYSTEM::ANONYMOUS-LAMBDA):
  BASE : "four"

现在我可以看到这个东西有一个名字。我将字符串传递"four"给带有名为base.

使用 REPL 和调试工具进行交互式开发很常见。最好准备对这种开发风格有用的代码。Common Lisp 没有经过优化,无法为完整的程序编译器提供广泛的类型检查——就像在 Haskell 中一样。

Lisp的主要问题之一是很难找出一段代码的真正作用。默认(带有前缀语法的严格函数式程序)相对容易理解。但是有很多可能性可以改变 Lisp 中代码的含义(宏、读取宏、符号宏、元对象协议、建议......)。

第一条规则:如果您正在编写基本的 Lisp 代码,请坚持使用基本的句法和语义可能性。防御性地写。期望其他人需要理解代码。为此,代码应该是可读的、易于理解的、使用常见的成语并且应该是可调试的。

在 Haskell 中,许多具有数学背景的人希望以非常紧凑的方式编写具有高度抽象性的代码。你也可以在 Lisp 中做到这一点。但是对于普通代码,我不会走这条路,对于较大的代码,Lisp 经常使用其他机制(通过宏进行代码转换,...​​)。

于 2013-03-22T11:41:44.123 回答
9

您可以为此类表单开发任意特殊语法。有多种变体。例如,我使用受 Clojure 启发的尖反引号语法。使用它,您的表单将如下所示:

#`(do-stuff % y)
于 2013-03-22T11:38:36.087 回答
5

我不认为你可以直接做到这一点,但是......

如果你知道你总是想做一些等价的事情(lambda (x) (fun x lexical))并且只是想要一种更短的表达方式,那么理论上你可以使用宏。

我个人建议不要这样做,(lambda (x) (fun x lex))不需要太多的打字,并从你的代码中删除一层晦涩难懂的东西。但是,如果它是一种足够常见的模式,需要进行特殊处理,则可能会执行以下操作:

(defmacro section (function lexical)
   (let ((sym (gensym))
     `(lambda (,sym) (,function ,sym ,lexical))))

这使得 Haskell 部分:

(`doStuff` y)

成为 Common Lisp 部分:

(section dostuff y)

因此,我并没有发现它更具可读性,至少在短期内,但如果它是我一次又一次看到的东西,我确实会考虑(并且已经这样做,更多是出于实验目的而不是其他任何事情)宏以使其更快(我在某处有一个半生不熟的宏,它允许您执行(_ func _2 lexical _1)->之类的操作*(lambda (a b) (func b lexical a)),有时很方便,但并没有真正提高可读性)。

于 2013-03-22T11:10:15.413 回答
3

Let Over Lambda中有一个尖锐的反引号读取宏可以适用于这种情况:

CL-USER>
(print
  '#`,(+ a1 y))

(LAMBDA (A1) (+ A1 Y))
(LAMBDA (A1) (+ A1 Y))

CL-USER>
(let ((y 2))
  (mapcar #`,(+ a1 y)
          (list 1 2 3 4)))
(3 4 5 6)
CL-USER>

这种方法与@Vsevolod Dyomkin 提到的技术非常相似。Hoyte 的版本确实有一些额外的特性,比如用任意数量的参数构建一个 lambda。另一方面,它有点难以解析,因为它是以更高级别的符号表示的,为了评估一个表单,你必须取消引用反引号(在这个例子中使用',')。

于 2013-03-24T07:41:33.823 回答
3

Scheme 有cut宏(在 SRFI-26 中),它允许您在使用<>. 例如:

(cut doStuff <> y)  ;; same as (lambda (x) (doStuff x y))
(cut - 5 <> 6 <> 7) ;; same as (lambda (x y) (- 5 x 6 y 7))

您可能可以在 CL 中定义类似的内容。

于 2013-03-25T13:05:10.107 回答
2

我也错过了使用普通 lisp 时 Haskell 风格的函数柯里化和组合的易用性。结果,我编写了以下包,它定义了 lisp 中简洁的 curry 和组合的读取器宏(它使用 alexandria 函数)。

http://eschulte.github.io/curry-compose-reader-macros/

有了这个包(mapcar (compose (curry #'* 2) (curry #'+ 1)) (list 1 2 3 4))就变成了(mapcar [{* 2} {+ 1}] (list 1 2 3 4))。我现在在几乎所有的 CL 项目中都使用它,并发现它大大减少了代码大小并提高了可读性。

于 2013-08-20T23:13:42.800 回答
1

alexandria 包导出符号curryrcurry. 因此,在您的情况下,您只需执行 (alexandria:rcurry function arg)例如(rcurry #'do-staff y).

Rcurry 和 curry 返回函数,因此您需要像往常一样调用结果。

于 2013-04-01T10:32:37.720 回答