Функции
Если вы изучали другие языки по типу Java, Python, C++ и им подобным, вы, наверно, знаете, что такое функции. Однако в Unison они работают немного по-другому, поэтому я буду описывать с нуля.
Функция - объект, который при передаче других объектов вычисляет результат. Посмотрим на примере:
plusOne : Nat -> Nat
plusOne n1 =
use Nat +
n1 + 1
> plusOne 41
В сигнатуре функции мы видим два типа со стрелочкой посередине. Последний тип в цепочке - тип результата, остальные - типы аргументов (передающихся объектов):
plusOne : Nat -> Nat --- результат
|
------------- аргумент
То есть в этой функции 1 аргумент. Следующим мы видим название функции, затем название аргумента:
plusOne n1
Так же мы видим здесь строку:
use Nat +
Если мы посмотрим в прошлую главу, то вспомним, что есть несколько определений оператора «+». Через use мы можем указать, какую версию оператора или терма мы хотим использовать. Вникнем в это позже, но важно знать, что Unison умеет сам выводить и добавлять use, так что большую часть времени вам не потребуется это писать.
И последним идет само рассчитывающееся выражение:
n1 + 1
При выполнении функции значение переменной подставится, выполнится расчет и вернется значение. Стоит помнить, что последнее выражение в функции - это и есть результат функции.
Сохраним файл и посмотрим, что нам выведет Unison:
quickstart/main>
Loading changes detected in ~/unisonlanguage/scratch.u.
+ plusOne : Nat -> Nat
Run `update` to apply these changes to your codebase.
6 | > plusOne 41
⧩
42
quickstart/main>
Вот, он рассчитал нам функцию! Теперь, по такой же схеме сделаем вторую функцию:
plusNums : Nat -> Nat -> Nat
plusNums n1 n2 =
n1 + n2
> plusNums 41 1
И чтобы точно запомнилось:
plusNums : Nat -> Nat -> Nat --- результат
| |
-------------------- аргументы
В терминале у нас отображается следующее:
5 | > plusNums 41 1
⧩
42
Так можно добавлять сколько угодно элементов, ограничений нет.
Еще одной особенностью является то, что в Unison функции каррированы, в отличии от других языков. Что это значит? Рассмотрим на примере:
plusThree : Nat -> Nat -> Nat -> Nat
Эта функция принимает три аргумента, но по факту, под капотом это выглядит вот так:
plusThree : Nat -> (Nat -> (Nat -> Nat))
То есть по факту мы можем передать один аргумент, чтобы получить функцию, принимающую два аргумента, затем ей передать второй, чтобы получить функцию, принимающую один аргумент, и уже ей передать последний аргумент, чтобы получить результат.
Звучит сложно, я знаю. Но на деле это проще чем кажется. Например, мы можем из нашей функции plusNums повторить функцию plusOne (если не удалили старую реализацию, сделайте это перед вводом):
plusOne = plusNums 1
И получим функцию, работающую точно так же и имеющую такую же сигнатуру:
+ plusOne : Nat -> Nat
Это полезно во многих ситуациях, не стоит недооценивать подобную особенность.
Переменные
В функции можно точно так же, как и просто на верхнем уровне, объявлять переменные. Это удобно для хранения промежуточных результатов. Пример:
plusThree : Nat -> Nat -> Nat -> Nat
plusThree n1 n2 n3 =
plusTwo = n1 + n2
plusTwo + n3
Сейчас (и дальше) на подобных простых примерах я не буду выводить результат UCM: думаю, и так понятно, что делает эта функция.
Блоки
В Unison блоком называется участок кода, сгруппированный вместе для организации. Блок выделяется через отступы, можно еще дополнительно обозначать его через let. Отступы важны и имеют значение, и не только в этом конкретном примере, по мере изучения инструментов это будет видно. Также блоки могут быть вложены друг в друга. Покажу на примере с официального сайта Unison:
nesting : Text
nesting =
parent = "outer"
inner1 = let
child1 = "child1"
inner2 = let
child2 = "child2"
parent
inner2
inner1
Анонимные функции и функции высшего порядка
На самом деле, в функциональном программировании это очень важная тема, которую нужно понять для легкой и продуктивной работы. Я постараюсь объяснить все максимально просто.
Анонимные функции (лямбда-функции) - это такие же функции, но без имени. Во всем остальном они полностью схожи с обычными функциями.
А функции высшего порядка - это функции, которые в аргументах принимают функции. В Unison есть такая особенность, что функция тоже может быть аргументом, и из-за этого появляется множество возможностей. Рассмотрим на примерах:
a = [1, 2, 3, 4, 5]
> List.map (elem -> elem + 1) a
> List.map
(i -> let
x = i + 1
y = x + 1
z = y + 1
z) [1, 2, 3]
19 | > List.map (elem -> elem + 1) a
⧩
[2, 3, 4, 5, 6]
20 | > List.map
⧩
[4, 5, 6]
Обратите внимание сначала на аргумент List.map. Это скобочки, в которых до стрелочки идут аргументы (если несколько аргументов, они разделяются пробелом), а после стрелочки - выражения. И видно, что лямбда-функции тоже поддерживают блоки, но let в них обязателен. Кстати, обычные функции с именем тоже можно передавать как аргумент, если подходит сигнатура (это вы увидите в следующей главе).
И заодно вы увидели пример функции высшего порядка. В следующей главе я расскажу про List, коллекции в целом и другие подобные функции.