【问题标题】:Sequential procedures in LispLisp 中的顺序过程
【发布时间】:2015-10-13 13:57:39
【问题描述】:

当我尝试使用不可变对象以函数式风格进行编程时,顺序操作最终会被由内而外地编写,如下所示:

(thing-operation3
  (thing-operation2
    (thing-operation1 thing extra-arg1)
    extra-arg2)
  extra-arg3)

我开始看到这种模式在我的代码中重复出现,我发现它很难阅读。使用 curry 和 compose 等高阶程序可以稍微改善这一点:

((compose1
  (curryr thing-operation3 extra-arg3)
  (curryr thing-operation2 extra-arg2)
  (curryr thing-operation1 extra-arg1))
 thing)

也许更好,但它仍然是颠倒的,并且需要一些额外的认知负担才能弄清楚发生了什么。而且我不确定这是否是理想的 Lisp 代码。

面向对象的风格更容易阅读:

thing.operation1(extra-arg1).operation2(extra-arg2)
    .operation3(extra-arg3)

它以自然顺序读取,也可以用不可变对象来实现。

ideomatic 在 Lisp 中编写这种顺序操作以使其易于阅读的方式是什么?

【问题讨论】:

    标签: functional-programming scheme lisp common-lisp racket


    【解决方案1】:

    怎么样

    (reduce (lambda (a b) (funcall b a)) 
        (list thing 
              (partial-apply op1 arg1) 
              (partial-apply op2 arg2) 
              ... 
              (partial-apply opn argn) ))
    

    (在Common Lisp)。在Racket

    (foldl (lambda (a b) (a b))
       thing (list 
              (partial-apply op1 arg1) 
              (partial-apply op2 arg2) 
              ... 
              (partial-apply opn argn) ))
    

    关于术语,它是((curry fun) arg)(partial-apply fun arg)

    【讨论】:

      【解决方案2】:

      Common Lisp 中的常用方法是使用 LET*

      (let* ((thing1 (thing-operation0 thing0 extra-arg0))
             (thing2 (thing-operation1 thing1 extra-arg1))
             (thing3 (thing-operation2 thing2 extra-arg2)))
        (thing-operation3 thing3 extra-arg3))
      

      这样可以命名返回值,提高可读性,并且可以为这些值编写声明。

      也可以编写一个宏,其用法如下:

      (pipe
       (thing-operation1 thing extra-arg1)
       (thing-operation2 _2    extra-arg2)
       (thing-operation3 _3    extra-arg3)
       (thing-operation4 _4    extra-arg4))
      

      某些语言提供类似的宏,而 Lisp 库可能会提供它的变体。让我们写一个简单的版本:

      (defmacro pipe (expression &rest expressions)
        (if (null expressions)
            expression
          (destructuring-bind ((fn arg &rest args) &rest more-expressions)
              expressions
            (declare (ignorable arg))
            `(pipe
              (,fn ,expression ,@args)
              ,@more-expressions))))
      

      对于上述pipe 表达式,生成以下代码:

      (THING-OPERATION4
       (THING-OPERATION3
        (THING-OPERATION2
         (THING-OPERATION1 THING EXTRA-ARG1)
         EXTRA-ARG2)
        EXTRA-ARG3)
       EXTRA-ARG4)
      

      一个变种:

      (defmacro pipe (expression &rest expressions)
        (if (null expressions)
            expression
          (destructuring-bind ((fn arg &rest args) &rest more-expressions)
              expressions
            `(pipe
              (let ((,arg ,expression))
                (,fn ,arg ,@args))
              ,@more-expressions))))
      

      这会让你写:

      (pipe (+ 1000 pi)
            (+ arg1 arg1)         ; use the previous result multiple times
            (+ arg2 (sqrt arg2))) ; use the previous result multiple times
      

      【讨论】:

      • 有一个名为 arrows 的库实现了这些功能,可从 Quicklisp 获得。
      【解决方案3】:

      Clojure 有一个线程运算符 ->,它可以满足您的期望:

      (-> thing
          (thing-operation1 extra-arg1)
          (thing-operation2 extra-arg2)
          (thing-operation3 extra-arg3))
      

      您可以轻松地将其实现为其他 Lisp 方言中的宏。例如,Greg Hendershott 的 rackjure 库有一个 ~> 表单,它在 Racket 中做同样的事情。

      ->(或 rackjure 中的 ~>)宏将结果拼接为每个子表单的第一个参数。如果您想将结果拼接为最后一个参数,可以使用->> 宏(rackjure 中的~>>)。

      【讨论】:

      • arrows 为 Common Lisp 实现了类似的功能。
      【解决方案4】:

      您可以使用PROGN Common Lisp 特殊形式。

      或者你可以定义你自己的 Lisp 宏来适应你的口味。

      【讨论】:

      • 谢谢。也许问题不清楚,每个操作都需要一个“事物”和一些额外的参数,并返回一个新的“事物”,下一个操作应该使用它。我想以一种清晰且不过分冗长的方式链接这些操作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-22
      • 1970-01-01
      相关资源
      最近更新 更多