Что более читабельно: функциональный Clojure или императивный Groovy?

StackOverflow https://stackoverflow.com/questions/1726943

Вопрос

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

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

Что делают «должности»?

Редактировать:упрощено согласно предложению 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
Это было полезно?

Решение

Я не думаю, что существует такая вещь, как внутренняя читаемость.Есть то, к чему вы привыкли, и то, к чему вы не привыкли.Мне удалось прочитать обе версии вашего кода.На самом деле мне было бы легче прочитать вашу версию 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 ужасен;Мне вообще не составило труда это прочитать, и я не гуру.)

Другие советы

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

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

редактировать: может быть уже не актуально.

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

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

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

Для меня проблема в двух аспектах:

  1. Насколько легко на первый взгляд понять, что делает код?Это важно для сопровождающих кода.

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

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

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

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

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

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

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

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

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

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 в течение трех лет, чтобы понять код и внести в него изменения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top