2012-04-24 1 views
9

Недавно я начал изучать Clojure. Как правило, это выглядит интересно, но я не могу привыкнуть к некоторым синтаксическим неудобствам (по сравнению с предыдущим опытом Ruby/C#).Переход от инфикса к префиксной нотации

Префиксная нотация для вложенных выражений. В Ruby я привык писать сложные выражения с цепочкой/конвейером слева направо: some_object.map { some_expression }.select { another_expression }. Это очень удобно, когда вы переходите от входного значения к результату шаг за шагом, вы можете сосредоточиться на одном преобразовании, и вам не нужно перемещать курсор при вводе. В отличие от этого, когда я пишу вложенные выражения в Clojure, я пишу код из внутреннего выражения в внешний, и мне приходится постоянно перемещать курсор. Он замедляется и отвлекает. Я знаю около -> и ->> макросов, но я заметил, что это не идиоматично. У вас была такая же проблема, когда вы начали кодирование в Clojure/Haskell и т. Д.? Как вы его решили?

+0

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

+0

Майк, подумайте, что вам нужно написать выражение '(filter # (expr1) (map # (expr2) expr3))'. Вы начнете вводить текст из внутреннего expr3 или из фильтра? – Alexey

+3

Обычно я начинаю со стороны и прокладываю себе путь. Поэтому я думаю о том, чего хочу, и просто отступаю от этого. –

ответ

3

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

initial + scale + offset 

стал

(+ initial scale offset) 

, а затем попытаться (+) префикс обозначения позволяет функции указать их собственную идентичность значений

user> (*) 
1 
user> (+) 
0 

Есть еще много примеров, и моя задача - не защищать префиксную нотацию. Я просто надеюсь передать, что кривая обучения сглаживается (эмоционально), когда становятся очевидными положительные стороны.

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


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


пс: (.. object object2 object3) ->object().object2().object3();

(doto my-object 
    (setX 4) 
    (sety 5)` 
+0

Артур, так ли вам, что префиксная нотация так же удобна для написания кода как инфиксного вызова '.methods' с точкой, или это действительно менее удобно, но имеет другие преимущества? – Alexey

+1

Как только я нашел макрос '..', я нахожу флексивную нотацию в jave очень громоздкой и трудно читаемой и печатной. (это просто мое скромное мнение) –

+1

@ArthurUlfeldt, не могли бы вы рассказать о «конечно, когда вы начинаете писать макросы, тогда наложение нотации становится обязательным, а не удобством»? Или вы хотели сказать здесь префикс? – ivant

4

К моему знанию -> и ->> являются идиоматическими в Clojure. Я использую их все время, и, на мой взгляд, они обычно приводят к гораздо более читаемому коду.

Вот некоторые примеры этих макросов, используемых в популярных проектах со всего "экосистема" Clojure:

Proof пример :)

9

Я чувствовал то же самое о Lisps изначально, поэтому я чувствую вашу боль :-)

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

Причины нравится префикс обозначение:

  • Согласованность с функциями - большинство языков используют сочетание инфикса (математические операторы) и префикс (функциональный вызов) обозначение. В Lisps все согласовано, что имеет определенную элегантность, если вы считаете, что математические операторы являются функциями.
  • Макросы - становятся намного более разумными, если вызов функции всегда находится в первой позиции.
  • Varargs - Приятно иметь переменное количество параметров для почти всех ваших операторов. (+ 1 2 3 4 5) симпатичнее ИМХО чем 1 + 2 + 3 + 4 + 5

Трюк тогда использовать -> и ->> librerally, когда он делает логический смысл структурировать ваш код таким образом. Это обычно полезно при работе с последующими операциями над объектами или коллекциями, например.

(->> 
    "Hello World" 
    distinct 
    sort 
    (take 3)) 

==> (\space \H \W) 

Финальный трюк, который я нашел очень полезно при работе в стиле приставки, чтобы сделать хорошее использование отступа при создании более сложных выражений. Если вы отступ правильно, то вы увидите, что префикс обозначение фактически совершенно ясно прочитать:

(defn add-foobars [x y] 
    (+ 
    (bar x y) 
    (foo y) 
    (foo x))) 
+0

Неудобство с '->' и '- >>' состоит в том, что у вас есть два из них. Некоторые функции ('cons',' map', 'apply') принимают составной объект как последний аргумент, а другие (' conj', 'update-in') как первый, и вы не можете привязать их togather с помощью одного' -> 'или '-' >>. ПостскриптумВ чистых OOP-языках, таких как нотация Ruby и Smalltalk infix, полностью согласуется: '1. + (2), [1] .zip ([2])' и составной объект всегда является аргументом перед точкой. – Alexey

+3

@Alexey: Существует причина для '->' и '- >>' и порядок сортировки аргументов. Это связано с различием между абстракцией seq и коллекциями. Я думаю, что это смущает, потому что сбор часто рассматривается как seq. Я часто ссылаюсь на объяснение Богатого Хикки - http://groups.google.com/group/clojure/msg/a8866d34b601ff43 –

+2

Также, если вы действительно этого хотите, библиотека swiss-стрелок предоставляет макрос алмазной палочки: http: // stackoverflow .com/q/10068398/148578 –

3

Если у вас есть длинное выражение цепи, используйте let. Длинные убегающие выражения или глубоко вложенные выражения не являются особенно читаемыми на любом языке. Это плохо:

(do-something (map :id (filter #(> (:age %) 19) (fetch-data :people)))) 

Это немного лучше:

(do-something (map :id 
        (filter #(> (:age %) 19) 
          (fetch-data :people)))) 

Но это тоже плохо:

fetch_data(:people).select{|x| x.age > 19}.map{|x| x.id}.do_something 

Если мы читаем это, что мы должны знать? Мы вызываем do_something на некоторые атрибуты некоторого подмножества people. Этот код трудно читать, потому что между первым и последним существует так много расстояния, что мы забываем, что мы смотрим, к тому времени, когда мы путешествуем между ними.

В случае с Ruby do_something (или что-то другое, что дает окончательный результат) теряется в конце линии, поэтому трудно сказать, что мы делаем с нашим people. В случае с Clojure сразу становится очевидным, что do-something - это то, что мы делаем, но трудно сказать, что мы делаем, не читая все это внутри.

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

Я предпочел бы что-то вроде этого:

(let [people (fetch-data :people) 
     adults (filter #(> (:age %) 19) people) 
     ids (map :id adults)] 
    (do-something ids)) 

Теперь это очевидно: я начинаю с people, я лох вокруг, а потом я do-something к ним.

И вы могли бы уйти с этого:

fetch_data(:people).select{|x| 
    x.age > 19 
}.map{|x| 
    x.id 
}.do_something 

Но я бы, наверное, предпочел бы сделать это, по крайней мере:

adults = fetch_data(:people).select{|x| x.age > 19} 
do_something(adults.map{|x| x.id}) 

Это также не неслыханное использовать let, даже если ваши посреднические выражения не имеют хороших имен. (Этот стиль иногда используется в собственном исходном коде Clojure в, например, исходный код для defmacro)

(let [x (complex-expr-1 x) 
     x (complex-expr-2 x) 
     x (complex-expr-3 x) 
     ... 
     x (complex-expr-n x)] 
    (do-something x)) 

Это может быть большая помощь в отладке, так как вы можете проверить вещи в любой момент, выполнив:

(let [x (complex-expr-1 x) 
     x (complex-expr-2 x) 
     _ (prn x) 
     x (complex-expr-3 x) 
     ... 
     x (complex-expr-n x)] 
    (do-something x))