Является ли функциональным Clojure или императивным Groovy более читаемым?

Хорошо, теперь не обманывать.

Нет, действительно, займите минуту или две и попробуйте это.

Что делают "позиции"?

Изменить: упрощено согласно предложению 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, чтобы понять его.

Итак, мой вопрос: это вопрос о том, чтобы больше использовать функциональные шаблоны программирования? Являются ли функциональные программисты гуру, читающим этот код, и нахождение его для понимания? Какую версию you найти легче понять?

Изменить: что этот код делает, вычисляет позиции игроков в соответствии с их точками, а отслеживает тех, кто привязан. Например:


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

Ответы

Ответ 1

Я не думаю, что есть такая вещь, как внутренняя читаемость. Там, к чему вы привыкли, и к чему вы не привыкли. Я смог прочитать обе версии вашего кода в порядке. Я мог бы лучше читать вашу версию Groovy более легко, хотя я не знаю Groovy, потому что я тоже потратил десятилетие на C и Java и только год смотрел на Clojure. Это ничего не говорит о языках, это только говорит обо мне.

Точно так же я могу читать английский легче, чем испанский, но это ничего не говорит о внутренней читаемости этих языков. (Испанский на самом деле, вероятно, является "более удобочитаемым" языком этих двух с точки зрения простоты и последовательности, но я до сих пор не могу его прочитать). Я изучаю японский язык прямо сейчас и переживаю тяжелое время, но родные японцы говорят то же самое о английском.

Если вы потратили большую часть своей жизни на чтение Java, конечно, вещи, которые выглядят как Java, будут легче читать, чем те, которые этого не делают. До тех пор, пока вы не потратите столько времени на изучение языков Lispи, как на C-подобных языках, это, вероятно, останется верным.

Чтобы понять язык, среди прочего, вы должны быть знакомы с:

  • ([vector] vs. (list), hyphens-in-names) Словарь
  • (что означает reductions? Как/где вы можете его найти?)
  • правила оценки (обрабатывает ли функции как объекты? Это ошибка на большинстве языков.)
  • идиомы, например (map first (some set of reductions with extra accumulated values))

Все это требует времени, практики и повторения, чтобы учиться и усваивать. Но если вы потратите следующие 6 месяцев на чтение и написание большого количества Clojure, вы не только сможете понять этот код Clojure через 6 месяцев, вы, вероятно, поймете его лучше, чем сейчас, и, возможно, даже быть в состоянии упростить его. Как насчет этого:

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

Глядя на код Clojure, который я написал год назад, я в ужасе от того, насколько это плохо, но я могу прочитать его в порядке. (Не сказал, что ваш код Clojure ужасен, мне вообще нечего было его читать, и я не гуру.)

Ответ 2

edit: может быть неактуально больше.

Clojure один из них запутан. Он содержит больше абстракций, которые нужно понимать. Это цена использования функций более высокого порядка, вы должны знать, что они означают. Поэтому в изолированном случае императив требует меньше знаний. Но сила абстракций находится в их средствах сочетания. Каждый обязательный цикл должен быть прочитан и понят, тогда как абстракции последовательности позволяют удалить сложность цикла и объединить мощные операции.

Я хотел бы далее утверждать, что версия Groovy, по крайней мере, частично функциональна, поскольку использует коллекцию, которая на самом деле является картой, функцией более высокого порядка. В нем есть определенное состояние.

Вот как я напишу версию 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 в том, что он использует изменяемый "текущий", но отличается тем, что у него нет следующей/предыдущей переменной, вместо этого вместо этого используются неизменяемые последовательности. Как выразился Брайан, читаемость не является неотъемлемой. Эта версия является моим предпочтением для этого конкретного случая и, кажется, сидит где-то посередине.

Ответ 3

Я согласен с Тимоти: вы вводите слишком много абстракций. Я переработал ваш код и закончил с:

(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))

Ответ 4

Clojure один более запутанный с первого взгляда; хотя это может быть более элегантно. OO - результат, который делает язык более "относительным" на более высоком уровне. Функциональные языки, по-видимому, имеют более "алгоритмический" (примитивный/элементарный) смысл. Это то, что я чувствовал в данный момент. Возможно, это изменится, когда у меня будет больше опыта работы с clojure.

Я боюсь, что мы уступаем игре, язык которой может быть наиболее кратким или решить проблему в наименьшей строке кода.

Проблема для меня в 2 раза:

  • Насколько легко на первый взгляд получить представление о том, что делает код?. Это важно для разработчиков кода.

  • Насколько легко угадать логику кода?. Слишком многословный/длинный?. Слишком сложно?

"Сделайте все как можно проще, но не проще".

Альберт Эйнштейн

Ответ 5

Я тоже учусь Clojure и люблю его. Но на этом этапе моего развития версию Groovy было легче понять. Что мне нравится в Clojure, хотя читает код и имеет "Ага!" когда вы, наконец, "получите" то, что происходит. То, что мне действительно нравится, - это тот же опыт, который случается несколько минут спустя, когда вы понимаете все способы применения кода к другим типам данных без каких-либо изменений кода. Я потерял подсчет количества раз, когда я работал с некоторым числовым кодом в Clojure, а потом, немного позже, подумал о том, как этот же код можно использовать со строками, символами, виджетами,...

Аналогия, которую я использую, касается обучения цветам. Помните, когда вы были введены в красный цвет? Вы поняли это довольно быстро - там весь этот красный материал в мире. Затем вы услышали термин "пурпурный" и некоторое время были потеряны. Но опять же, после немного более разоблачения, вы поняли концепцию и имели более конкретный способ описать определенный цвет. Вы должны усвоить концепцию, получить немного больше информации в своей голове, но вы получите что-то более мощное и сжатое.

Ответ 6

Groovy поддерживает различные стили решения этой проблемы:

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

определенно не реорганизован, чтобы быть красивым, но не слишком сложно понять.

Ответ 7

Я знаю, что это не ответ на вопрос, но я смогу "понять" код намного лучше, если есть тесты, такие как:

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... Другой, более подходящий, пример: если несколько строк кода облегчают понимание для программистов "beginner" Clojure, тогда код в целом будет более читабельным, потому что это будет понято более крупному сообществу: нет необходимости изучили Clojure в течение трех лет, чтобы понять код и внести в него изменения.