Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Функции

Если вы изучали другие языки по типу 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, коллекции в целом и другие подобные функции.