如何编写可读的Clojure代码?


13

我是Clojure的新手。我可以理解我编写的代码,但是以后很难理解。
括号很难匹配。

在各种情况下,有关命名约定和缩进的通用约定是什么?

例如,我写了一个示例性的解构示例来理解,但是第二次看起来却完全不可读。

(defn f [{x :x y :y z :z [a b c] :coll}] (print x " " y  " " z " " a " " b " " c)) 

在进行解构的情况下,最好直接在参数级别执行此操作,还是启动一个let表格然后再继续执行?


3
关于堆栈溢出的可读性有一个很好的答案。您可以签出。stackoverflow.com/a/1894891/1969106
yfklon 2013年

2
通常,编写可读的Lisp代码很困难。他们发明了反义词“失去多余的括号”是有原因的。
梅森惠勒

Answers:


23

命名约定

  • 保持小写功能
  • 使用-了连字符(这将是在其他语言中的下划线或骆驼的情况下)。

    (defn add-one [i] (inc i))

  • 谓词(即返回true或false的函数)以? 示例结尾:odd? even? nil? empty?

  • 状态更改程序以结尾!。你还记得set!吧?要么swap!

  • 选择简短的变量名长度,具体取决于它们的范围。这意味着,如果您的辅助变量很小,则通常只能使用一个字母的名称。(map (fn [[k v]] (inc v)) {:test 4 :blub 5})根据需要选择更长的变量名,尤其是当它们用于大量代码行并且您无法立即猜测其用途时。(我的想法)。

    我觉得很多Clojure程序员倾向于使用通用名称和简称。但这当然不是一个客观的观察。关键是实际上很多clojure函数都是通用的。

Lambda函数

  • 您实际上可以命名lambda函数。这对于调试和分析非常方便(我的经验是使用ClojureScript)。

    (fn square-em [[k v]] {k (* v v)})

  • 使用内联lambda函数#()尽可能方便

空格

  • 不应只有parens行。即立即关闭括号。请记住,parens是用于编辑器和编译器的,缩进适合您。

  • 功能参数列表换行

   (defn缺点
     [ab]
     (清单ab))

如果您考虑文档字符串,这是有道理的。它们在函数名称和参数之间。以下文档字符串可能不是最明智的;)

   (defn缺点
     “对事物进行配对”
     [ab]
     (清单ab))
  • 只要保留配对,配对数据就可以用新行分隔
  (defn f 
    [{x:x 
      y:y 
      Z Z  
      [abc]:coll}] 
    (打印x“” y“” z“” a“” b“” c)) 

(您也可以根据需要输入,,但这感觉不拘一格)。

  • 对于缩进,请使用足够好的编辑器。多年前,这是用于Lisp编辑的emacs,今天的vim​​也很棒。典型的clojure IDE也应提供此功能。只是不要使用随机文本编辑器。

    在命令模式下的vim中,可以使用=命令正确缩进。

  • 如果命令太长(嵌套等),则可以在第一个参数后插入换行符。现在,以下代码是毫无意义的,但它说明了如何对表达式进行分组和缩进:

(+(if-let [age(:personal-age coll)]
     (如果(> 18岁)
       年龄
       0))
   (计数(范围(-3 b)
                 (减少+ 
                         (范围b 10)))))

好的缩进意味着您不必计算括号。括号用于计算机(解释源代码并对其进行缩进)。缩进是为了您容易理解。

高阶函数与fordoseq形式

来自Scheme的背景,我为能够理解maplambda函数等感到非常自豪。因此,我经常会写这样的东西

(map (fn [[k x]] (+ x (k data))) {:a 10 :b 20 :c 30})

这很难读。该for形式是更好的方法:

(for [[k x] {:a 10 :b 20 :c30}]
  (+ x (k data)))

map有很多用途,如果您使用命名函数,那真的很棒。即

(map inc [12 30 10]

(map count [[10 20 23] [1 2 3 4 5] (range 5)])

使用线程宏

使用线程宏->->>以及doto时适用。

关键是线程宏使源代码看起来比函数组合更线性。如果没有线程宏,下面的代码几乎是不可读的:

   (f (g (h 3) 10) [10 3 2 3])

与之比较

   (-> 
     (h 3)
     (g 10)
     (f [10 3 2 3]))

通过使用线程宏,通常可以避免引入仅使用一次的临时变量。

其他事情

  • 使用文档字符串
  • 保持功能简短
  • 阅读其他Clojure代码

带有解构功能的函数通过缩进看起来很漂亮!
Amogh Talpallikar,

+1用于保持功能简短。许多小功能更多地是自我记录
Daniel Gratzer

1
强烈不同意使用简短的变量名,即使在“短距离”函数中也是个好主意。好的变量名对于可读性至关重要,除了击键外,它们什么都不花钱。这是Clojure社区最让我困扰的事情之一。许多人对描述性变量名几乎怀有敌意。Clojure核心堆满了用于函数参数的1个字母的变量名,这使得学习该语言变得更加困难(例如,运行docsource在REPL中)。咆哮的结尾,一个另外的很好的答案
内森·华莱士

@NathanWallace在某种程度上我同意您的意见,但在某些方面我不同意。长名称有时会使功能过于具体。因此,您可能会发现某些常规过滤器操作实际上是常规的,而当参数apples不是时xs,您认为它特定于苹果。然后,我还认为函数参数名称比for for循环变量更进一步。因此,如果需要,您可以将它们放长一点。作为最后一个念头:我会离开你“名称代码不重视” concatenative.org/wiki/view/Concatenative%20language/...
wirrbel

我可能会在私有与公共接口之类的地方添加一段。特别是关于图书馆。这是代码质量的一个方面,没有被充分讨论,自编写该答案以来,我已经学到了很多。
wirrbel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.