如何使Clojure程序的结构更容易辨识?

3
作为Lisp方言,Clojure继承了Lisp的同像性。同像性使元编程更容易,因为代码可以被视为数据:语言中的反射(在运行时检查程序实体)依赖于单一、均匀的结构,并且不必处理出现在复杂语法中的几种不同结构[1]
更加均匀的语言结构的缺点是语言构造(例如循环、嵌套if语句、函数调用或switch语句等)彼此更加相似。
在Clojure中:
   ;; if:
   (if (chunked-seq? s)
     (chunk-cons (chunk-first s) (concat (chunk-rest s) y))
     (cons (first s) (concat (rest s) y)))

   ;; function call:
   (repaint (chunked-seq? s)
     (chunk-cons (chunk-first s) (concat (chunk-rest s) y))
     (cons (first s) (concat (rest s) y)))

两种结构之间的区别只在一个单词上。在非同像语言中:
// if:
if (chunked-seq?(s))
    chunk-cons(chunk-first(s), concat(chunk-rest(s), y));
else
   cons(first(s), concat(rest(s), y));

// function call:
repaint(chunked-seq?(s),
        chunk-cons(chunk-first(s), concat(chunk-rest(s), y)),
        cons(first(s), concat(rest(s), y));

有没有办法在Clojure中使这些程序结构更容易识别(更加显眼)?也许有一些推荐的代码格式或最佳实践吗?

1
我会在(repaint后面加上一个换行符,或者像你的"C"示例中那样缩进。在一个合适的编辑器中使用paredit、彩虹括号、吞吐等功能,你很快就会用不同(非花括号语言)的眼光看待这个问题。 - cfrick
1个回答

5
除了使用支持不同情况语法高亮的IDE之外,实际上在代码本身中没有区分它们的方法。您可以尝试使用格式设置来区分函数调用和宏。
(for [a b]
  [a a])

(some-func [a b] [a a])

但这样会防止您使用一个 for 的一行列表推导式; 有时它们可以整齐地放在一行上。这也会阻止您将大型函数调用分成几行。除非缩减函数是预定义的,否则我对 reduce 的大多数调用都采取以下形式:

(reduce (fn [a b] ...)
        starting-acc
        coll)

有太多情况需要尝试限制调用的格式。那么像cond这样更复杂的宏呢?

我认为理解一个表单的操作完全取决于表单中的第一个符号是关键。不要依赖特殊的语法来区分它们,训练你的眼睛迅速转向表单中的第一个符号,并在脑海中快速“查找”。

而且,实际上只需要考虑几种情况:

  • 特殊形式,如iflet(实际上是let*)。这些是语言的基本结构,因此您将不断接触到它们。

    • 我认为这些不应该成为问题。当您看到if时,您的大脑应该立即知道发生了什么。特殊形式很少,因此最好的方法是纯记忆。
  • 具有“不寻常”行为的宏,例如线程宏和cond。仍然有一些情况下,我会查看别人的代码,因为他们正在使用我不太熟悉的宏,这会花费我一些时间来弄清楚代码的流程。

    • 通过练习使用宏来解决这个问题。学习新的宏可以扩展您编写Clojure的能力,因此这应始终被考虑。与特殊形式一样,真正令人费解的宏并不多,因此记忆主要的宏(基本线程宏和条件宏)很简单。
  • 函数。如果不是以上两种情况,则必须是一个函数,并遵循典型的函数调用语法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接