Потокові макроси
Потокові макроси, також відомі як функції–стрілки, використовуються для запису вкладених викликів функцій у простішому форматі, що більш зрозумілий для читання.
Уявіть, що у вас є вираз (f (g (h x)))
, в якому функція f
отримує результат обчислення функції g
і так далі, з навіть більшою кількістю вкладених викликів. За допомогою потокового макросу ->
такий вираз можна записати як (-> x (h) (g) (f))
, що набагато легше читати.
Це синтаксичний цукор, бо функція–стрілка насправді є макросом, що перетворює вираз (-> x (h) (g) (f))
в (f (g (h x)))
на етапі компілювання коду в JavaScript.
Прийміть до уваги, що круглі дужки навколо h
, g
та f
— необов'язкові, якщо ці функції не мають додаткових параметрів. Тобто вираз (f (g (h x)))
можна записати, як (-> x h g f)
.
Макрос першого потоку (->
)
Його називають макросом першого потоку тому, що він встановлює значення першого аргументу для всіх виразів.
Нумо розглянемо наступний приклад без потокового макросу:
(def book {:name "Lady of the Lake"
:readers 0})
(update (assoc book :age 1999) :readers inc)
;; => {:name "Lady of the lake" :age 1999 :readers 1}
З макросом ->
це буде виглядати так:
(-> book
(assoc :age 1999)
(update :readers inc))
;; => {:name "Lady of the lake" :age 1999 :readers 1}
У ClojureScript функції, що трансформують структури даних завжди приймають дані першим аргументом. Тому цей потоковий макрос дуже зручний у використанні, коли треба записати підряд декілька таких трансформацій.
Макрос останнього потоку (->>
)
Основна відмінність макросу останнього потоку від макросу першого потоку у тому, що макрос ->>
встановлює значення останнім аргументом в усіх виразах.
Давайте розглянемо приклад:
(def numbers [1 2 3 4 5 6 7 8 9 0])
(take 2 (filter odd? (map inc numbers)))
;; => (3 5)
Той самий приклад з використанням макросу ->>
:
(->> numbers
(map inc)
(filter odd?)
(take 2))
;; => (3 5)
Цей потоковий макрос дуже зручний у використанні, коли треба трансформувати послідовності або колекції даних. Бо у ClojureScript функції, що трансформують послідовності та колекції, завжди приймають дані останнім аргументом.
Потоковий макрос (as->
)
Інколи може бути так, що макроси першого та останнього потоку незручно використовувати. В таких випадках використовується макрос as->
, що дозволяє встановити значення на довільне місце для кожного виразу окремо, а не лише в початок чи кінець виразу.
Цей макрос приймає два значення і довільну кількість виразів. Як і в звичайному потоковому макросі, перший аргумент — це значення, яке буде встановлено аргументом у всі вирази. А от другим аргументом повинен бути символ, з яким воно буде зв’язане. Цей символ використовується у всіх виразах в макросі для встановлення значення на довільне місце в аргументах функцій.
Давайте розглянемо приклад:
(as-> numbers $
(map inc $)
(filter odd? $)
(first $)
(hash-map :result $ :id 1))
;; => {:result 3 :id 1}
Потокові макроси some->
та some->>
Ще одна пара потокових макросів для спеціальних випадків. Вони працюють майже так само, як макроси ->
та ->>
, але мають можливість припинити виконання послідовності виразів, якщо один з них поверне nil
.
Розглянемо такий приклад:
(some-> (rand-nth [1 nil])
(inc))
;; => 2
(some-> (rand-nth [1 nil])
(inc))
;; => nil
Це простий спосіб уникнення помилок, коли nil
передається в функцію, яка цього не чекає.
Потокові макроси cond->
та cond->>
Макроси cond->
та cond->>
також схожі на ->
та ->>
. Але вони дозволяють пропускати виконання виразів за допомогою умови, що записується перед кожним виразом.
Розглянемо приклад:
(defn describe-number
[n]
(cond-> []
(odd? n) (conj "odd")
(even? n) (conj "even")
(zero? n) (conj "zero")
(pos? n) (conj "positive")))
(describe-number 3)
;; => ["odd" "positive"]
(describe-number 4)
;; => ["even" "positive"]
Тут виконуються лише ті вирази, для яких умова обчислюється в логічне true
.