Если у вас есть длинное выражение цепи, используйте 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))
Я думаю, что это только идиоматично, потому что это то, к чему вы привыкли. Префиксная нотация намного более последовательна, чем инфикс, и поэтому ее можно будет легко читать, как только вы привыкнете к ней. –
Майк, подумайте, что вам нужно написать выражение '(filter # (expr1) (map # (expr2) expr3))'. Вы начнете вводить текст из внутреннего expr3 или из фильтра? – Alexey
Обычно я начинаю со стороны и прокладываю себе путь. Поэтому я думаю о том, чего хочу, и просто отступаю от этого. –