2013-03-22 99 views
9

在Haskell,如果我有一个lambda看起来像下面哈斯克尔式部分

(\x -> doStuff x y) 

其中y是从周围的范围,我可以部分它,把它变成

(`doStuff` y) 

它更短,更简洁(也是我最喜欢哈斯克尔的东西之一)。

现在,在Common Lisp中我会写的等价代码为

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

这实际上是我在写一个很平常的事,但我觉得即使是样板的点点困扰我一些,所以我想知道是否有办法像Common Lisp中的Haskell风格的部分那样?

+0

嗯,我的意思是简洁简洁......虽然我对CL还是比较新的,但不知道从哪里开始,我只涉及宏的基本知识。 @downvoter - 谨慎评论? – jaymmer 2013-03-22 11:11:32

+0

@wvxvw我不明白你评论的第一部分。难道不能假设如果OP使用诸如“简明”这样的主观描述符来表明他或她正在陈述观点? – 2013-03-22 13:23:26

+2

关于“......可以常规显示......”的内容,如果有的话,我会对引文感兴趣。此外,我觉得你的哪个例子更简洁可能取决于观众的背景。如果一个人没有任何数学背景,我怀疑第一个例子更容易理解。除此之外,我的观点是,这种平庸的行为总体而言对本论坛的功能和目的有害。 – 2013-03-22 14:34:03

回答

11

除非你是有经验的,我建议你学会用Lisp写Lisp的,没怎么用Lisp写哈斯克尔。后者不是一个好主意。 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经常使用其他机制(通过宏代码转换,...​​)。

+0

谢谢 - 尽管我绝对没有试图学习如何在Lisp中编写Haskell,但我不想成为那些放弃某种语言或最终写出糟糕/违背惯例的代码的人,他们认为“它甚至没有做x“,而它实际上确实做了x,我只是不知道它。 – jaymmer 2013-03-22 21:33:14

+3

三件事(其中两件是nitpicks)。首先,你的咖喱没有解决问题;所描述的不仅仅是currying,而是一个句法特征(对第二个参数应用一个函数)。其次,Haskell的大部分开发都发生在REPL上,我认为这很典型。另一方面,尽管GHCi(标准编译器的REPL)有一个内置的调试器,但我几乎没有使用它,而且我认为这也是典型的(但不那么重要)。第三,我认为“数学背景”与您是否发现'\ x - > doStuff x y','flip doStuff y'或'(\'doStuff \'y)'更具可读性无关。 – 2013-03-23 00:34:47

5

我不认为你可以直接这样做,但是......

如果你知道你总是希望做一些事情,相当于(lambda (x) (fun x lexical))和只是想表达的是更短的方式,你可以理论上使用宏。

我个人建议不要这样做,(lambda (x) (fun x lex))不需要太多的输入并从代码中移除一层默默无闻的东西。但是,如果它是足够常见,它值得特别处理,像下面可以做这样一个规律:

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

这使得哈斯克尔部分:

(`doStuff` y) 

成为Common Lisp的部分:

(section dostuff y) 

我不这样做,因为这样,发现它更具可读性,至少在短期内,但如果那件事,我也一次又一次地看到,我的确会考虑(和所做的一切,更多的实验目的比其他任何东西)一个宏,使其更快(我有一个半烤的宏,在某处,它允许你做的事情,如(_ func _2 lexical _1) - >*(lambda (a b) (func b lexical a)),这有时很方便,但并不真正提高可读性)。

9

您可以为这些表单开发任意特殊语法。有多种变体。例如,我使用Clojure灵感sharp-backquote syntax。使用它,你的表格看起来就像这样:

#`(do-stuff % y) 
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。另一方面,解析起来有点困难,因为它是以更高级别的表示法表示的,为了评估表单,您必须取消引用反引号(在本例中使用',')。

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中定义类似的东西。

1

亚历山大包出口符号curryrcurry。 所以在你的情况下,你只要做 (alexandria:rcurry function arg),例如,(rcurry #'do-staff y)

Rcurry和咖喱返回函数,所以你需要像往常一样调用结果。

2

我还错过了Haskell风格的函数在容易理解的情况下在常见的Lisp中进行函数压缩和合成。因此,我写了下面的包,它定义了读者宏在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项目中都使用它,并且发现它大大减少了代码大小并提高了可读性。