功能性Clojure或命令式Groovy更具可读性吗?

时间:2009-11-13 03:27:23

标签: groovy functional-programming clojure imperative-programming

好的,现在没有作弊。

不,真的,花一两分钟试试这个。

“职位”有什么作用?

编辑:根据cgrand的建议简化。

(defn redux [[current next] flag] [(if flag current next) (inc next)])

(defn positions [coll]
  (map first (reductions redux [1 2] (map = coll (rest coll)))))

现在,这个版本怎么样?

def positions(coll) {
  def (current, next) = [1, 1]
  def previous = coll[0]
  coll.collect {
    current = (it == previous) ? current : next
    next++
    previous = it
    current
  }
}

我正在学习Clojure,我喜欢它,因为我一直很喜欢函数式编程。我花了更长的时间来提出Clojure解决方案,但我很高兴能够思考优雅的解决方案。 Groovy解决方案没问题,但是我发现这种命令式编程无聊和机械。经过12年的Java,我觉得Clojure的功能和编程是我需要的提升。

对,明白了。好吧,我必须诚实地说,我想知道我几个月后回到它时是否会理解Clojure代码。当然我可以评论它,但我不需要评论我的Java代码来理解它。

所以我的问题是:这是一个越来越习惯函数式编程模式的问题吗?函数式编程大师是否正在阅读此代码并轻松理解它? 哪个版本更容易理解?

编辑:这段代码的作用是根据玩家的分数计算玩家的位置,同时跟踪那些被捆绑的玩家。例如:


Pos Points
1. 36
1. 36
1. 36
4. 34
5. 32
5. 32
5. 32
8. 30

7 个答案:

答案 0 :(得分:22)

我认为没有内在可读性这样的东西。这是你习惯的,你不习惯的。我能够读取您的代码的两个版本。我实际上可以更轻松地阅读你的Groovy版本,即使我不知道Groovy,因为我也花了十年时间研究C和Java,而且只用了一年时间看Clojure。这并没有说出语言的任何内容,只是说了些关于我的事情。

同样地,我比西班牙语更容易阅读英语,但这也没有说明这些语言的内在可读性。 (在简单性和一致性方面,西班牙语实际上可能是两者中“更易读”的语言,但我仍然无法阅读它)。我现在正在学习日语,并且经历了一段艰难时期,但母语为日语的人对英语也是如此。

如果你一生中大部分时间都在阅读Java,那么看起来像Java的东西比那些没有Java的东西更容易阅读。直到你花了很多时间看Lispy语言看看类似C语言,这可能仍然存在。

要了解某种语言,您必须熟悉这些语言:

  • 语法([vector](list)hyphens-in-names
  • 词汇(reductions是什么意思?你怎么样/在哪里查找它?)
  • 评估规则(将函数视为对象是否有效?在大多数语言中都是错误的。)
  • 成语,如(map first (some set of reductions with extra accumulated values))

所有这些都需要时间,练习和重复来学习和内化。但是如果你在接下来的6个月里阅读和撰写大量的Clojure,你不仅可以在6个月后理解Clojure代码,你可能会比现在更好地理解它,甚至可以简化它。怎么样:

(use 'clojure.contrib.seq-utils)                                        ;;'
(defn positions [coll]
  (mapcat #(repeat (count %) (inc (ffirst %)))
          (partition-by second (indexed coll))))

看看我一年前写过的Clojure代码,我对它有多糟糕感到震惊,但我能读得很好。 (不是说你的Clojure代码太可怕了;我完全没有阅读它,我也不是大师。)

答案 1 :(得分:8)

编辑:可能不再相关。

Clojure对我来说很复杂。它包含更多需要理解的抽象。这是使用更高阶函数的代价,你必须知道它们的含义。因此,在一个孤立的案例中,必要性需要较少的知识。但抽象的力量在于它们的组合方式。必须读取和理解每个命令循环,而序列抽象允许您消除循环的复杂性并组合强大的操作。

我会进一步争辩说Groovy版本至少部分功能,因为它使用了collect,它实际上是map,一个更高阶的函数。它也有一些状态。

以下是我将如何编写Clojure版本:

(defn positions2 [coll]
  (let [current (atom 1)
        if-same #(if (= %1 %2) @current (reset! current (inc %3)))]
    (map if-same (cons (first coll) coll) coll (range (count coll)))))

这与Groovy版本非常相似,因为它使用了一个可变的“当前”,但不同之处在于它没有next / prev变量 - 而是使用不可变序列。正如Brian所说,可读性不是内在的。这个版本是我对这个特殊情况的偏好,似乎坐在中间的某个地方。

答案 2 :(得分:8)

我同意Timothy:你引入了太多的抽象。我重写了你的代码并以:

结束
(defn positions [coll]
  (reductions (fn [[_ prev-score :as prev] [_ score :as curr]] 
                (if (= prev-score score) prev curr))
    (map vector (iterate inc 1) coll)))

关于您的代码,

(defn use-prev [[a b]] (= a b))
(defn pairs [coll] (partition 2 1 coll))
(map use-prev (pairs coll))

可以简单地重构为:

(map = coll (rest coll))

答案 3 :(得分:4)

乍一看,Clojure更令人费解;虽然它可能更优雅。 OO是使语言在更高层次上更“相关”的结果。 功能语言似乎具有更多“算法”(原始/基本)的感觉。 这正是我此刻的感受。 当我有更多使用clojure的经验时,这可能会改变。

我担心我们正在降低哪种语言可以最简洁的解决方案,或者在最少的代码行中解决问题。

这个问题对我来说有两个问题:

  1. 乍看之下如何轻松了解代码的作用? 这对代码维护者很重要。

  2. 猜测代码背后的逻辑是多么容易? 太冗长/啰嗦?太简洁了?

  3. “让一切变得尽可能简单,但并不简单。”

    阿尔伯特爱因斯坦

答案 4 :(得分:3)

我也在学习Clojure并喜欢它。但是在我开发的这个阶段,Groovy版本更容易理解。我喜欢Clojure的是阅读代码并拥有“啊哈!”当你最终“得到”正在发生的事情时的体验。我真正享受的是几分钟之后发生的类似体验,当您意识到代码可以应用于其他类型的数据而不改变代码的所有方式时。我已经失去了在Clojure中通过一些数字代码工作的次数,然后,不久之后,我想到了相同的代码如何与字符串,符号,小部件一起使用......

我使用的类比是学习颜色。还记得你被介绍红色的时候?你很快就明白了 - 世界上有这些红色的东西。然后你听到洋红色这个词已经丢失了一段时间。但是,经过多一点曝光后,您了解了这个概念,并且有一种更具体的方式来描述特定的颜色。你必须内化这个概念,掌握更多的信息,但你最终会得到更强大和简洁的东西。

答案 5 :(得分:3)

Groovy也支持各种解决此问题的方式:

coll.groupBy{it}.inject([]){ c, n -> c + [c.size() + 1] * n.value.size() }

绝对不会重构为漂亮但不太难理解。

答案 6 :(得分:3)

我知道这不是问题的答案,但如果有测试,我将能够更好地“理解”代码,例如:

assert positions([1]) == [1]
assert positions([2, 1]) == [1, 2]
assert positions([2, 2, 1]) == [1, 1, 3]
assert positions([3, 2, 1]) == [1, 2, 3]
assert positions([2, 2, 2, 1]) == [1, 1, 1, 4]

这将告诉我,一年之后,代码应该做什么。比我在这里看到的任何优秀版本的代码要好得多。

真的偏离主题吗?

另一方面,我认为“可读性”取决于上下文。这取决于谁将维护代码。例如,为了维护Groovy代码的“功能”版本(无论多么精彩),不仅需要Groovy程序员,还需要功能强大的Groovy程序员...... 另一个更相关的例子是:如果几行代码使“初学者”Clojure程序员更容易理解,那么代码将更具可读性,因为它将被更大的社区理解:无需学习Clojure三年,能够掌握代码并对其进行编辑。