defn, анонимные fn и форма #()

Учимся определять функции — главный строительный блок любой программы на Clojure.

Функция в Clojure — значение первого класса: её можно передавать в другие функции, возвращать и хранить в переменных, как любое число или строку.

Именованные функции через defn

Самый частый способ — defn: имя, вектор параметров, тело. Последнее выражение тела — возвращаемое значение (никакого return не нужно).

(defn квадрат [x]
  (* x x))

(квадрат 5)  ; => 25

(defn сумма [a b]
  (+ a b))

(сумма 3 4)  ; => 7

Вывод:

25
7

Анонимные функции через fn

Иногда функция нужна на месте, без имени — например, чтобы передать её в map. Для этого есть fn:

((fn [x] (* x 10)) 4)  ; => 40

; обычно анонимную функцию передают другой функции
(map (fn [x] (+ x 100)) [1 2 3])  ; => (101 102 103)

Вывод:

40
(101 102 103)

Краткая форма #()

Для коротких анонимных функций есть ещё компактнее запись — #(...). Внутри % означает первый аргумент, %1, %2 — первый, второй и т.д.

; #(* % %) то же, что (fn [x] (* x x))
(map #(* % %) [1 2 3 4])   ; => (1 4 9 16)

; два аргумента: %1 и %2
(#(+ %1 %2) 10 20)         ; => 30

; сравнение: оставить элементы меньше 5
(filter #(< % 5) [3 7 2 9 4])  ; => (3 2 4)

Вывод:

(1 4 9 16)
30
(3 2 4)

Несколько арностей

Одна функция может принимать разное число аргументов — это называется арностью:

(defn привет
  ([] (привет "мир"))
  ([имя] (str "Привет, " имя)))

(привет)        ; => "Привет, мир"
(привет "Аня")  ; => "Привет, Аня"

Вывод:

"Привет, мир"
"Привет, Аня"

Как работает под капотом

Каждая функция Clojure компилируется в отдельный класс JVM, реализующий интерфейс IFn с методом invoke. Вызов (f x) — это вызов invoke у объекта функции. Поэтому функции и есть полноценные объекты-значения: их можно хранить и передавать. Форма #(...) — синтаксический сахар, который reader разворачивает в обычный fn с автоматически сгенерированными параметрами.

Частые ошибки

  • Вкладывать #() в #(). Внутри краткой формы нельзя вложить другую #() — символы % запутаются. Используйте fn.
  • Писать return. Его нет: возвращается последнее выражение тела.
  • Забыть экранировать сравнения. В коде (filter #(< % 5) ...) символ «меньше» — это оператор, аргумент подставляется через %.

Итоги

  • defn определяет именованную функцию; возвращается последнее выражение тела.
  • fn — анонимная функция; #(...) — её краткая форма с %, %1, %2.
  • Функция может иметь несколько арностей (наборов параметров).
  • Функции — значения первого класса, их можно передавать и возвращать.
Проверьте себя
1. Что возвращает функция, определённая через defn?
AВсегда nil
BЗначение, указанное в return
CЗначение последнего выражения тела
DПервый аргумент
2. Что означает % в краткой форме #()?
AОстаток от деления
BПервый аргумент функции
CКомментарий
DТекущую коллекцию
3. Чему эквивалентна запись #(* % %)?
A(fn [x] (+ x x))
B(fn [x] (* x x))
C(fn [x y] (* x y))
D(fn [] (* 2 2))