Как читать мысленно код Lisp / Clojure
большое спасибо за красивые ответы! Нельзя отметить только один как правильный
Примечание: уже Вики
Я новичок в функциональном программировании, и хотя я могу читать простые функции в функциональном программировании, например, вычисляя факториал числа, мне трудно читать большие функции.
Отчасти причина в том, что я думаю, что из-за моей неспособности выяснить меньшие блоки кода в определении функции и также отчасти потому, что мне становится трудно соответствовать ( ) в коде.
Примечание: я могу понять этот код, если я смотрю на него в течение 10 минут, но я сомневаюсь, что этот же код был написан на Java, это займет у меня 10 минут. Итак, я думаю, чтобы чувствовать себя комфортно в коде стиля Lisp, я должен это сделать быстрее
примечание: Я знаю, что это субъективный вопрос. И я не ищу здесь никакого доказуемо правильного ответа. Просто комментарии о том, как вы идете о чтении этого кода, будет приветствоваться и очень полезно
(defn concat
([] (lazy-seq nil))
([x] (lazy-seq x))
([x y]
(lazy-seq
(let [s (seq x)]
(if s
(if (chunked-seq? s)
(chunk-cons (chunk-first s) (concat (chunk-rest s) y))
(cons (first s) (concat (rest s) y)))
y))))
([x y & zs]
(let [cat (fn cat [xys zs]
(lazy-seq
(let [xys (seq xys)]
(if xys
(if (chunked-seq? xys)
(chunk-cons (chunk-first xys)
(cat (chunk-rest xys) zs))
(cons (first xys) (cat (rest xys) zs)))
(when zs
(cat (first zs) (next zs)))))))]
(cat (concat x y) zs))))
3 ответов:
код Lisp, в частности, еще труднее читать, чем другие функциональные языки из-за регулярного синтаксиса. Войцех дает хороший ответ для улучшения вашего семантического понимания. Вот некоторая помощь по синтаксису.
во-первых, при чтении кода, не волнуйся за скобки. Беспокоиться о вмятинах. Общее правило состоит в том, что вещи на одном и том же уровне отступа связаны. Итак:
(if (chunked-seq? s) (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) (cons (first s) (concat (rest s) y)))во-вторых, если вы не можете поместить все на одной строке, отступ следующей строки a небольшое количество. Это почти всегда два пространства:
(defn concat ([] (lazy-seq nil)) ; these two fit ([x] (lazy-seq x)) ; so no wrapping ([x y] ; but here (lazy-seq ; (lazy-seq indents two spaces (let [s (seq x)] ; as does (let [s (seq x)]в-третьих, если несколько аргументов функции не могут поместиться в одной строке, выровняйте второй, третий и т. д. аргументы под первой стартовой скобкой. Многие макросы имеют аналогичное правило с вариациями, чтобы позволить важным частям появляться первыми.
; fits on one line (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) ; has to wrap: line up (cat ...) underneath first ( of (chunk-first xys) (chunk-cons (chunk-first xys) (cat (chunk-rest xys) zs)) ; if you write a C-for macro, put the first three arguments on one line ; then the rest indented two spaces (c-for (i 0) (< i 100) (add1 i) (side-effects!) (side-effects!) (get-your (side-effects!) here))
эти правила помогут вам найти блоки в коде: если вы видите
(chunk-cons (chunk-first s)не считайте скобки! Проверьте следующую строку:
(chunk-cons (chunk-first s) (concat (chunk-rest s) y))вы знаете, что первая строка не является полным выражением, потому что следующая строка имеет отступ под ним.
если вы видите
defn concatсверху вы знаете, что у вас есть три блока, потому что есть три вещи на одном уровне. Но все, что ниже третьей строки, имеет отступы под ней, поэтому остальное принадлежит этому третьему блоку.вот руководство по стилю для схемы. Я не знаю Clojure, но большинство из них правила должны быть одинаковыми, поскольку никто другой шепелявит сильно различаться.
Я думаю
concatэто плохой пример, чтобы попытаться понять. Это основная функция, и она более низкоуровневая, чем код, который вы обычно пишете сами, потому что она стремится быть эффективной.еще одна вещь, чтобы иметь в виду, что код Clojure является чрезвычайно плотным по сравнению с кодом Java. Немного кода Clojure делает много работы. Тот же код в Java не будет 23 линии. Вероятно, это будет несколько классов и интерфейсов, очень много методов, много локальных временных выбрасываемые переменные и неудобные циклические конструкции и вообще все виды шаблона.
некоторые общие советы все же...
старайтесь игнорировать родителей большую часть времени. Вместо этого используйте отступ (как предлагает Натан Сандерс). например,
(if s (if (chunked-seq? s) (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) (cons (first s) (concat (rest s) y))) y))))когда я смотрю на это мой мозг видит:
if foo then if bar then baz else quux else blarfесли вы поместите курсор на paren и ваш текстовый редактор не синтаксис-выделите соответствующий, я предлагаю вам найти новый редактор.
иногда это помогает читать код наизнанку. Код Clojure имеет тенденцию быть глубоко вложенным.
(let [xs (range 10)] (reverse (map #(/ % 17) (filter (complement even?) xs))))плохое: Итак, мы начинаем с чисел от 1 до 10. Затем мы меняем порядок отображения фильтрации дополнения ожидания, о котором я забыл, о чем говорю."
хорошо: " хорошо, так что мы берем некоторые
xs.(complement even?)означает противоположность четного, поэтому "нечетный". Так мы фильтруем некоторую коллекцию, поэтому остаются только нечетные числа. Тогда мы разделим их всех на 17. Тогда мы изменим их порядок. А тоxsв вопросе от 1 до 10, понял."иногда это помогает сделать это явно. Возьмите промежуточные результаты, бросьте их в
letи дать им имя, чтобы ты понял. REPL сделан для игры вокруг, как это. Выполните промежуточные результаты и посмотрите, что дает каждый шаг вы.(let [xs (range 10) odd? (complement even?) odd-xs (filter odd? xs) odd-xs-over-17 (map #(/ % 17) odd-xs) reversed-xs (reverse odd-xs-over-17)] reversed-xs)скоро вы сможете делать такие вещи мысленно без усилий.
сделать либеральное использование
(doc). Полезность наличия документации, доступной прямо в REPL, нельзя переоценить. Если вы используетеclojure.contrib.repl-utilsи ваш .clj файлы на пути к классам, вы можете сделать(source some-function)чтобы увидеть исходный код. Вы можете сделать(show some-java-class)и посмотреть описание всех методов в нем. И на.возможность быстро прочитать что-то приходит только с опытом. Lisp-это не труднее, чем любой другой язык. Так уж получилось, что большинство языков выглядят как C, и большинство программистов тратят большую часть своего времени на чтение этого, поэтому кажется, что синтаксис C легче читать. Практика практика практика.
во-первых, помните, что функциональная программа состоит из выражений, а не операторов. Например, форма
(if condition expr1 expr2)принимает свой 1-й arg в качестве условия для проверки логического falue, оценивает его, и если он eval'ed в true, то он оценивает и возвращает expr1, в противном случае оценивает и возвращает expr2. Когда каждая форма возвращает выражение, некоторые из обычных синтаксических конструкций, таких как THEN или ELSE keywords, могут просто исчезнуть. Обратите внимание, что здесьifсам вычисляет выражение как что ж.теперь об оценке: в Clojure (и других шепелявит) большинство форм можно с вызовы функций вида
(f a1 a2 ...), где все аргументыfвычисляются перед фактическим вызовом функции; но формы могут быть также макросы или специальные формы, которые не оценивают некоторые (или все) его аргументы. Если вы сомневаетесь, обратитесь к документации(doc f)или просто проверить в REPL:
user=> applyфункция
#<core$apply__3243 clojure.core$apply__3243@19bb5c09>user=> doseqмакрос.
java.lang.Exception: Can't take value of a macro: #'clojure.core/doseqэти два правила:
- у нас есть выражения, а не утверждения
- оценка подформы может произойти или нет, в зависимости от того, как ведет себя внешняя форма
должно облегчить ваш groking программ Lisp, esp. если у них есть хороший отступ, как пример вы дали.
надеюсь, что это помогает.
Comments