Является ли функциональный Clojure или императив Groovy более читабельным? - PullRequest
15 голосов
/ 13 ноября 2009

ОК, без мошенничества.

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

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

Редактировать: упрощено по предложению 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 ]

22 голосов
/ 13 ноября 2009

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

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

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

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

  • синтаксис ([vector] против (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 ужасен; у меня не было никаких проблем с его чтением, и я не гуру.)

8 голосов
/ 13 ноября 2009

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

(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))
8 голосов
/ 13 ноября 2009

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 в том, что она использует изменяемый «current», но отличается тем, что в ней нет переменной next / prev - вместо этого используются неизменяемые последовательности для них. Как красноречиво сказал Брайан, удобочитаемость не является внутренней. Эта версия - мое предпочтение в данном конкретном случае, и кажется, что она где-то посередине.

4 голосов
/ 18 ноября 2009

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

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

Вопрос для меня в 2 раза:

  1. Как легко на первый взгляд понять, что делает код ?. Это важно для разработчиков кода.

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

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

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

3 голосов
/ 19 ноября 2011

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

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

3 голосов
/ 27 июня 2010

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

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

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

3 голосов
/ 13 ноября 2009

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

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

...