Функції
Знайомство
Час переходити до активних дій! Функції у ClojureScript - це так звані об’єкти першого класу: за своєю поведінкою вони не відрізняються від інших типів. Такі функції можна передавати як аргументи або отримати як значення. Контекст при цьому завжди визначається статично. У певних ситуаціях ClojureScript підтримує динамічне визначення контексту, але про це ми поговоримо в іншому розділі.
За більш детальною інформацією щодо визначення контексту направляємо читачів до статті з Вікіпедії), у якій вичерпно роз’яснюються різні типи визначення контексту.
Для виклику функцій у ClojureScript використовується типова для діалектів Lisp префіксна нотація:
(inc 1)
;; => 2
У наведеному прикладі inc
- це функція та частина середовища виконання (рантайму) ClojureScript, а 1
- перший аргумент функції inc
.
(+ 1 2 3)
;; => 6
Символ +
представляє функцію додавання. На відміну від мов родини ALGOL, де знаком +
позначається оператор додавання, що допускає лише два параметри, у ClojureScript символ +
означає функцію, кількість параметрів якої не обмежена.
Префіксна нотація має значні переваги, в тому числі досить неочевидні. ClojureScript не розрізняє функції та оператори, кожен оператор - це функція. Префіксна нотація знімає обмеження на кількість аргументів будь-якого “оператора”, а також повністю усуває проблему їхнього пріоритету.
Визначення власних функцій
Неіменовану (анонімну) функцію можна визначити за допомогою спеціальної форми fn
. Це один з існуючих типів визначення функції. У наступному прикладі функція приймає два параметри та повертає їх середнє значення.
(fn [param1 param2]
(/ (+ param1 param2) 2.0))
Функцію можна визначити та викликати в одному виразі:
((fn [x] (* x x)) 5)
;; => 25
Приступимо до створення іменованих функцій. Але спочатку спитаємо себе, що таке іменована функція? Все дуже просто: у ClojureScript, функції - це об’єкти першого класу, що поводяться як будь-які інші значення, тому іменування функції - це її поєднання з певним символом.
(def square (fn [x] (* x x)))
(square 12)
;; => 144
Також у ClojureScript існує макрос defn
- синтаксичний цукор для більш виразного визначення функцій:
(defn square
"Return the square of a given number."
[x]
(* x x))
Рядок, між ім’ям функції та вектором параметрів, називається документаційним рядком (docstring). Ці рядки будуть використані програмами, що автоматично створюють веб-документацію з сирцевих файлів.
Мультиарні функції
ClojureScript підтримує можливість визначення мультиарних функції - функцій зі змінною кількістю аргументів (термін “арність” вказує на допустиму кількість аргументів функції ). За синтаксисом мультиарні функції відрізняються від звичайних наявністю більш ніж одного тіла функції.
Розглянемо на прикладі:
(defn myinc
"Self defined version of parameterized `inc`."
([x] (myinc x 1))
([x increment]
(+ x increment)))
Рядок : ([x] (myinc x 1))
означає, що за наявності лише одного аргументу слід викликати функцію myinc
із цим аргументом та числом 1
- у якості другого аргументу. Друге тіло функції ([x increment] (+ x increment))
означає, що якщо аргументів два, слід повернути результат додавання цих аргументів.
Наведемо приклади використання мультиарної функції, яку ми щойно визначили. Зверніть увагу: якщо ми викличемо функцію із невірною кількістю аргументів, компілятор повідомить про помилку.
(myinc 1)
;; => 2
(myinc 1 3)
;; => 4
(myinc 1 3 3)
;; Compiler error
ЗАУВАЖЕННЯ: Концепція арності знаходиться за межами тем цієї книги, але ви можете ознайомитися з нею за цим посиланням: Вікіпедія.
Варіативні функції
Інший спосіб обходження із невизначеною кількістю аргументів - це створення варіативних функцій. Варіативні функції - такі функції, що приймають довільну кількість аргументів:
(defn my-variadic-set
[& params]
(set params))
(my-variadic-set 1 2 3 1)
;; => #{1 2 3}
Синтаксис варіативної функції передбачає символ &
на початку вектора аргументів.
Скорочений синтаксис анонімних функцій
Для визначення анонімних функцій, ClojureScript пропонує скорочений синтаксис із використанням макросу читання #()
(як правило, використовується із однорядковими функціями). Макроси читання - це спеціальні вирази, що трансформуються у відповідну мовну форму під час компіляції. У цьому випадку - у вираз, що використовує спеціальну форму fn
.
(def average #(/ (+ %1 %2) 2))
(average 3 4)
;; => 3.5
Попередній вираз є скороченим записом наступного:
(def average-longer (fn [a b] (/ (+ a b) 2)))
(average-longer 7 8)
;; => 7.5
Символи %1
, %2
... %N
- це прості маркери позицій параметрів. Вони будуть імпліцитно оголошені під час інтерпретації макросу та конвертовані у вираз fn
.
Якщо функція приймає лише один аргумент, можна опустити число після символу %&
. Наприклад, функцію, що підносить число до другого степеня #(* %1 %1))
, можна записати так: #(* % %))
.
Синтаксис також дозволяє використання варіативної форми із символом %&
:
(def my-variadic-set #(set %&))
(my-variadic-set 1 2 2)
;; => #{1 2}