15

好的,现在不作弊了。

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

“职位”有什么作用?

编辑:根据 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 解决方案没问题,但我发现这种类型的命令式编程既无聊又机械。在使用 Java 12 年后,我觉得使用 Clojure 进行函数式编程是我需要的推动力。

对了,进入正题。好吧,我不得不说实话,我想知道几个月后我再看 Clojure 代码时是否能理解它。当然我可以评论它,但我不需要评论我的 Java 代码来理解它。

所以我的问题是:这是一个越来越习惯函数式编程模式的问题吗?函数式编程大师是否阅读了这段代码并发现它很容易理解?觉得哪个版本更容易理解?

编辑:此代码的作用是根据玩家的积分计算玩家的位置,同时跟踪并列的玩家。例如:


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

7 回答 7

22

我不认为有任何内在的可读性。有你习惯的,也有你不习惯的。我能够阅读您的代码的两个版本。我实际上可以更轻松地阅读您的 Groovy 版本,即使我不了解 Groovy,因为我也花了十年时间研究 C 和 Java,而只研究 Clojure 一年。这并没有说明语言,它只说明了我的一些事情。

同样,我可以比西班牙语更容易阅读英语,但这也没有说明这些语言的内在可读性。(就简单性和一致性而言,西班牙语实际上可能是两者中“更具可读性”的语言,但我仍然看不懂)。我现在正在学习日语并且过得很艰难,但是以日语为母语的人对英语也有同样的看法。

如果您一生中大部分时间都在阅读 Java,那么看起来像 Java 的东西当然会比看起来像 Java 的东西更容易阅读。除非您花在研究 Lispy 语言上的时间与研究类 C 语言的时间一样多,否则这可能仍然是正确的。

要理解一门语言,除其他外,您必须熟悉:

  • 语法 ( [vector]vs. (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 代码很糟糕;我阅读它完全没有问题,而且我不是专家。)

于 2009-11-13T04:52:17.063 回答
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 雄辩地指​​出的那样,可读性不是内在的。这个版本是我对这种特殊情况的偏好,似乎位于中间的某个地方。

于 2009-11-13T07:12:20.917 回答
8

我同意蒂莫西的观点:你引入了太多的抽象。我重新编写了您的代码并以以下方式结束:

(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))
于 2009-11-13T12:50:23.323 回答
4

Clojure 乍一看更加复杂。虽然它可能更优雅。OO 是使语言在更高层次上更“相关”的结果。函数式语言似乎有一种更“算法”(原始/基本)的感觉。这就是我此刻的感受。当我有更多使用 clojure 的经验时,也许这会改变。

恐怕我们正在陷入哪种语言可以最简洁或用最少的代码行解决问题的游戏。

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

  1. 乍一看,感觉代码在做什么是多么容易?这对代码维护者很重要。

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

“让一切尽可能简单,但不要更简单。”

艾尔伯特爱因斯坦

于 2009-11-18T02:44:15.203 回答
3

我也在学习 Clojure 并喜欢它。但是在我开发的这个阶段,Groovy 版本更容易理解。我喜欢 Clojure 的地方在于阅读代码并发出“啊哈!” 当你最终“得到”正在发生的事情时,体验一下。我真正喜欢的是几分钟后发生的类似体验,当您意识到代码可以应用于其他类型的数据而无需更改代码的所有方式时。我已经记不清有多少次我在 Clojure 中处理过一些数字代码,然后,过了一会儿,我想到了如何将相同的代码用于字符串、符号、小部件......

我使用的类比是关于学习颜色。还记得你被介绍给红色的时候吗?你很快就明白了——世界上有这么多红色的东西。然后你听到了洋红色这个词并且迷失了一段时间。但同样,经过多一点曝光后,你就理解了这个概念,并且有了一种更具体的方式来描述一种特定的颜色。你必须内化这个概念,在你的脑海中保留更多的信息,但你最终会得到更强大和简洁的东西。

于 2009-11-13T14:58:35.243 回答
3

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

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

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

于 2010-06-27T06:28:27.360 回答
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 三年即可掌握代码并进行编辑给它。

于 2011-11-18T23:36:20.923 回答