Common Lisp中的Haskell风格部分

时间:2013-03-22 10:57:17

标签: haskell lambda common-lisp

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

(\x -> doStuff x y)

其中y来自周围的范围,我可以将其划分并将其转换为

(`doStuff` y)

更简洁,更简洁(也是我最喜欢的Haskell之一)。

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

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

这对我来说实际上是一件非常普通的事情,但我觉得即使是那么一点点样板也让我感到烦恼,所以我想知道是否有办法在Common Lisp中获得类似Haskell风格的部分?

7 个答案:

答案 0 :(得分: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经常使用其他机制(通过宏代码转换,...​​...)。

答案 1 :(得分:9)

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

#`(do-stuff % y)

答案 2 :(得分: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))这样的事情,这有时很方便,但并不是真的提高可读性。)

答案 3 :(得分: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。另一方面,解析起来有点困难,因为它是以更高级别的表示法表达的,为了评估表单,你必须取消引用反引号(在本例中使用',')。

答案 4 :(得分: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中定义类似的东西。

答案 5 :(得分:2)

在普通的lisp中,我也错过了Haskell风格的函数currying和组合的简易性。因此,我编写了以下软件包,它定义了读取器宏,以便在lisp中使用简洁的咖喱和组合(它使用亚历山大函数)。

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项目中使用它,发现它大大减少了代码大小并提高了可读性。

答案 6 :(得分:1)

alexandria包导出符号curryrcurry。 所以在你的情况下你就是这么做的 (alexandria:rcurry function arg),例如(rcurry #'do-staff y)

Rcurry和curry返回函数,因此您需要照常执行结果。