【问题标题】:Haskell-style sections in Common LispCommon Lisp 中的 Haskell 样式部分
【发布时间】:2013-03-22 10:57:17
【问题描述】:

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

(\x -> doStuff x y)

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

(`doStuff` y)

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

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

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

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

【问题讨论】:

  • 嗯,我的意思是简明扼要...虽然我对 CL 还很陌生,不知道从哪里开始,我只接触了宏的基础知识。 @downvoter - 愿意发表评论吗?
  • @wvxvw 我不明白您评论第一部分的意义。不能假设如果 OP 使用诸如“简洁”之类的主观描述词,他或她正在陈述意见吗?
  • 关于“...可以常规显示...”的内容,如果您有引文,我会很感兴趣。此外,在我看来,你的哪个例子更简洁可能取决于听众的背景。如果一个人没有任何数学背景,我怀疑第一个例子更容易理解。除此之外,我认为这种程度的迂腐,总的来说,对这个论坛的功能和目的是有害的。

标签: haskell lambda common-lisp


【解决方案1】:

除非你更有经验,否则我建议你学习在 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 经常使用其他机制(通过宏进行代码转换,...​​)。

【讨论】:

  • 谢谢 - 虽然我绝对不想学习如何用 Lisp 编写 Haskell,但我只是不想成为那些放弃一门语言或最终写得不好的人/违反约定的代码认为“它甚至不做 x”,而实际上它确实做了 x,而我只是没有意识到这一点。
  • 三件事(其中两件是挑剔的)。首先,您的curry 没有解决问题;所描述的不仅仅是柯里化,而是一种语法特征(将函数应用于第二个参数)。其次,我的大部分 Haskell 开发都是在 REPL 中进行的,我认为这是典型的;另一方面,虽然 GHCi(标准编译器的 REPL)有一个内置的调试器,但我几乎没有使用过它,我认为这也是典型的(但不太常见)。第三,我认为“数学背景”与您是否觉得\x -> doStuff x yflip doStuff y(`doStuff` y) 更具可读性没有任何关系。
【解决方案2】:

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

#`(do-stuff % y)

【讨论】:

    【解决方案3】:

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

    如果您知道自己总是想做与(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)) 之类的事情,这有时很方便,但并没有真正提高可读性)。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-05-16
              • 1970-01-01
              • 2020-05-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-12-22
              • 1970-01-01
              相关资源
              最近更新 更多