Основы Unison
В Unison все начинается с «терма». Если по-простому, то терм - это название какого-либо значения или функции. Возьмем такой код:
helloWorld : Text
helloWorld = "Hello, World!"
Такой код объявит переменную helloWorld, которая имеет тип Text и хранит в себе строку «Hello, World!». Сразу скажу, что все переменные неизменяемы - это один из столпов функционального программирования впринципе, и это удобно, когда системы становятся сложными. Если не верите мне - дочитайте книгу и попробуйте сами сделать аналогичные системы на Unison и на, например, Python :) А пока продолжаем!
Из-за того, что Unison сам умеет понимать, что присваивается переменной, мы можем написать без первой строки:
helloWorld = "Hello, World!"
И все будет работать точно так же. Но для наглядности я буду писать типы переменной.
Создайте файл scratch.u в той же папке, в которой у вас запущен UCM. Затем напишите код и сохраните файл. В терминале вы сразу увидите что-то подобное:
Loading changes detected in ~/unisonlanguage/scratch.u.
+ helloWorld : Text
Run `update` to apply these changes to your codebase.
Наш UCM сам автоматически отслеживает файл и показывает, что добавилось, удалилось или изменилось. Попробуем написать update:
quickstart/main> update
Done.
quickstart/main>
Все, теперь ваша переменная может использоваться, даже не находясь в файле! Для наглядности можете попробовать очистить файл и вписать туда следующее:
> helloWorld
После сохранения файла в UCM сразу отобразится сообщение о том, что
- Не найдены изменения;
- «Вычислится» наше выражение (в данном случае - просто выведется).
quickstart/main>
Loading changes detected in ~/unisonlanguage/scratch.u.
No changes found.
1 | > helloWorld
⧩
"Hello, world!"
Данная стрелочка объявляет «контрольное выражение» - выражение, которое выводится при любом обновлении файла. Это довольно полезно при разработке, и я вам советую использовать их как можно чаще.
Также есть и другие базовые типы данных: натуральные, целые числа, числа с плавающей точкой, булева логика, строки, символы и байты:
isGood : Boolean -- булево
isGood = true
natural : Nat -- натуральное число
natural = 42
justNum : Int -- целое число
justNum = +42
pi : Float -- число с плавающей точкой
pi = 3.14
aChar : Char -- символ
aChar = ?a
blob : Bytes -- сырые байты
blob = 0xsdeadbeef
Прошу обратить внимание, что целые числа обязаны всегда иметь знак перед числом, ведь если будет не +42, а 42, число будет интерпретировано как натуральное. Также для Char перед любым символом нужно поставить знак вопроса, а для Bytes - применить шаблон 0xs<шестнадцатиричный код>.
Также в терминале у нас при сохранении отобразится следующее:
Loading changes detected in ~/unisonlanguage/scratch.u.
+ aChar : Char
+ blob : Bytes
+ isGood : Boolean
+ justNum : Int
+ natural : Nat
+ pi : Float
Run `update` to apply these changes to your codebase.
Пока нет смысла сохранять, лучше немного поработаем с нашими переменными. Например, посмотрим, что будет, если сложить натуральное число с целым:
I couldn't figure out what + refers to here:
20 | > natural + justNum
The name + is ambiguous. I tried to resolve it by type but no term with that
name would pass typechecking. I think its type should be:
Nat -> Int -> o
If that's not what you expected, you may have a type error somewhere else in
your code.
Help me out by using a more specific name here or adding a type annotation.
I found some terms in scope with matching names but different types. If one of
these is what you meant, try using its full name:
(Float.+) : Float -> Float -> Float
(Int.+) : Int -> Int -> Int
(Nat.+) : Nat -> Nat -> Nat
(Pattern.+) : Pattern a -> Pattern a -> Pattern a
(Class.+) : Class -> Class -> Class
(Bag.+) : Bag a -> Bag a -> Bag a
(Decimal.+) : Decimal -> Decimal -> Decimal
(Integer.+) : Integer -> Integer -> Integer
(Natural.+) : Natural -> Natural -> Natural
(Rational.+) : Rational -> Rational -> Rational
(Duration.+) : Duration -> Duration -> Duration
(Instant.+) : Instant -> Duration -> Instant
(LocalDate.+) : LocalDate -> Int -> LocalDate
(Deprecated.+) : Natural.Deprecated -> Natural.Deprecated -> Natural.Deprecated
Мы увидели, что UCM не знает, какой именно оператор выбрать, потому что для каждого типа он определен свой. И мы также можем догадаться, что у нас не получится сложить два разных типа, пускай они и очень похожи. И если мы все-таки хотим это сделать, нам придется одну из переменных привести к другому типу. Но пока что мы не будем это делать, лучше посмотрим, что еще интересного можно делать здесь и сейчас:
19 | > helloWorld ++ " Hello, Unison!"
⧩
"Hello, world! Hello, Unison!"
20 | > natural - 50
⧩
0
Оператор ++ определен для строк и складывает их, тут все понятно. А вот второй случай интереснее: как вы помните, natural у нас имеет значение 42, однако же ошибки не случилось. То есть мы можем не переживать о том, что у нас может получиться неопределенное поведение, так как Nat всегда положителен. Честно скажу: кроме этих двух особенностей я не нашел особых отличий с другими языками программирования: для чисел есть математические операции, для логики есть булевые операции, и т.п. Остальные операторы будут появляться в книге и остальные типы будут рассказаны вам чуть позже. А сейчас я хочу поговорить немного о документации.
Документация
В коде поддерживаются комментарии:
- два дефиса - для комментария в линию;
- {-<любой текст>-} - для мультистрочных комментариев;
- три дефиса - для разграничения файла; Последнее покажу на примере. Если мы впишем в файл следующее:
> helloWorld ++ " Hello, Unison!"
---
> natural - 50
Я люблю Unison
И сохраним, то UCM нам напишет следующее:
19 | > helloWorld ++ " Hello, Unison!"
⧩
"Hello, world! Hello, Unison!"
quickstart/main>
То есть второе выражение не было просчитано, и UCM даже не поругался на мое признание.
Но дело в том, что комментарии не «загружаются» в базу при ее обновлении, поэтому они используются только при разработке, а не для документации. Возникает закономерный вопрос: а как же тогда документировать и объяснять код?
И на помощь нам приходит особый тип Doc! Он определяется так: {{<текст>}}, и из коробки поддерживает форматирование, фото, видео, таблицы, Mermaid, LaTEX и даже исполнение кода внутри себя! Я ознакомлю вас с базовым функционалом, но, может, в конце книги оставлю полный список возможностей.
Чтобы добавить документацию к чему-либо, есть два способа:
- Он подходит для уже добавленных в базу объектов:
helloWorld.doc = {{Это - строка "Привет, мир!"}}
- Подходит для пишущихся сейчас объектов:
{{Очень важное число!}}
bestNumber = 42
После сохранения файла UCM пишет нам:
+ bestNumber : Nat
+ bestNumber.doc : Doc
+ helloWorld.doc : Doc
Готово, у нас есть документация для модуля! Теперь напишем в самом UCM следующее:
quickstart/main> docs bestNumber
Очень важное число!
quickstart/main>
Команда docs выводит документацию по объекту. То, что вам пригодится уже с первой минуты - это @source{<имя объекта>} для отображения исходников в Doc, @signature{<имя объекта>} для отображения сигнатуры объекта (это как раз тип объекта), а так же можно добавлять Doc внутри Doc. Давайте я вам покажу все три примера вместе:
{{
@signature{bestNumber}
Очень важное число!
@source{bestNumber}
{{bestNumber.doc.examples}}
}}
bestNumber = 42
bestNumber.doc.examples = {{
Примеры:
```
bestNumber + 1
```
}}
Сохраним файл и посмотрим на документацию в UCM:
quickstart/main> docs bestNumber
bestNumber : Nat
Очень важное число!
bestNumber : Nat
bestNumber = 42
Примеры:
bestNumber Nat.+ 1
⧨
43
quickstart/main>
Такая мощная документация позволяет ей поддерживать и обновлять саму себя, а также исчерпывающе объяснять и показывать все нужное разработчику. И я вам сразу дал эти знания, чтобы вы могли с первого дня строить не только красивые, но и понятные системы :)