Синтаксис Goat
Полное описание синтаксических конструкций языка с примерами.
Комментарии
Goat поддерживает два вида комментариев:
-- Однострочный комментарий
{- Многострочный
комментарий -}
fun add x y = x + y -- inline комментарий
Литералы
Базовые типы и их литеральный синтаксис:
| Тип | Примеры | Описание |
|---|---|---|
Int | 0, 42, -7 | 64-битное целое |
Float | 3.14, -0.5, 1.0e10 | 64-битное число с плавающей точкой |
Bool | true, false | Логический тип |
String | "hello", "line\n" | Строка в UTF-8 |
Unit | () | Единичный тип, аналог void |
Escape-последовательности в строках
| Последовательность | Значение |
|---|---|
\n | Перенос строки |
\t | Табуляция |
\r | Возврат каретки |
\\ | Обратный слеш |
\" | Двойная кавычка |
\' | Одиночная кавычка |
Операторы
Операторы перечислены от наименьшего к наибольшему приоритету:
| Приоритет | Оператор | Ассоциативность | Описание |
|---|---|---|---|
| 1 | |> | лево | Pipe: x |> f → f x |
| 1 | ~> | лево | Ленивый pipe: аргумент оборачивается в lazy |
| 2 | && | лево | Логическое И |
| 2 | || | лево | Логическое ИЛИ |
| 3 | == != /= | нет | Равенство / неравенство (!= и /= — синонимы) |
| 3 | < > <= >= | нет | Сравнение |
| 4 | ++ | право | Конкатенация строк и списков |
| 5 | :: | право | Cons: добавить элемент в начало списка |
| 6 | + - | лево | Сложение / вычитание |
| 7 | * / mod | лево | Умножение / деление / остаток |
| 8 | ** | право | Возведение в степень |
| 9 | not | префикс | Логическое отрицание |
| 10 | - | префикс | Унарный минус |
| 11 | применение | лево | f x y — применение функции |
-- Примеры операторов
let a = 2 ** 10 -- 1024
let b = 10 mod 3 -- 1
let s = "hello" ++ " world" -- "hello world"
let xs = 1 :: 2 :: 3 :: [] -- [1, 2, 3]
let r = xs |> reverse |> show -- pipeline
let — привязка значения
Привязывает имя к значению. Бывает глобальной (объявление верхнего уровня)
и локальной (внутри выражения через in).
Глобальная привязка
let pi = 3.14159
let greeting = "Hello, world!"
let doubled = map (\x -> x * 2) [1..5]
Локальная привязка (let … in)
let result =
let x = 10 in
let y = x * 2 in
x + y -- 30
let без in является
последовательной привязкой, а let! — монадической привязкой IO-действия.
fun — определение функции
Объявляет именованную функцию. Параметры перечисляются через пробел после имени.
Тело следует после =.
fun greet name =
"Hello, " ++ name ++ "!"
fun add x y = x + y
fun clamp lo hi x =
if x < lo then lo
else if x > hi then hi
else x
Каррирование и частичное применение
Все функции каррированы. fun add x y = x + y эквивалентно
fun add x = \y -> x + y. Частичное применение создаёт новую функцию:
let add5 = add 5 -- функция Int -> Int
let r = add5 3 -- 8
let doubled = map (add 0) [1..5]
Лямбда-выражения
Анонимные функции задаются через \params -> body:
let square = \x -> x * x
let add = \x y -> x + y
let evens = filter (\x -> x mod 2 == 0) [1..10]
let pairs = map (\x -> (x, x * x)) [1..5]
Рекурсия
Рекурсивные функции объявляются с ключевым словом rec:
fun rec factorial n =
if n <= 1 then 1
else n * factorial (n - 1)
fun rec fibFast a b n =
if n == 0 then a
else fibFast b (a + b) (n - 1)
rec имя функции недоступно внутри её тела.
Взаимная рекурсия пока не поддерживается.
if / then / else
Условное выражение. Всегда должна быть ветка else —
обе ветки должны возвращать значение одного типа.
let sign x =
if x > 0 then "positive"
else if x < 0 then "negative"
else "zero"
let abs x = if x < 0 then -x else x
match — сопоставление с образцом
Сопоставляет значение с последовательностью образцов (паттернов).
Каждая ветка: | паттерн => тело.
fun describe n =
match n with
| 0 => "zero"
| 1 => "one"
| _ => "many"
fun sumList xs =
match xs with
| [] => 0
| x :: rest => x + sumList rest
Гарды (when)
После паттерна можно добавить условие через when:
fun classify n =
match n with
| x when x < 0 => "negative"
| x when x == 0 => "zero"
| x when x < 10 => "small"
| _ => "large"
fun safeDiv x y =
match y with
| 0 => None
| d when d > 0 => Some (x / d)
| d => Some (-(x / (-d)))
Списки и диапазоны
Литеральные списки
let empty = []
let nums = [1, 2, 3, 4, 5]
let mixed = ["a", "b", "c"]
Cons-оператор ::
let xs = 1 :: 2 :: 3 :: [] -- [1, 2, 3]
let ys = 0 :: xs -- [0, 1, 2, 3]
Диапазоны
let r1 = [1..5] -- [1, 2, 3, 4, 5]
let r2 = [0..9] -- [0, 1, ..., 9]
let r3 = [1..100] -- все от 1 до 100
Конкатенация списков
let combined = [1, 2] ++ [3, 4] -- [1, 2, 3, 4]
Tuple
Кортежи фиксированного размера, могут содержать значения разных типов:
let pair = (1, "hello")
let triple = (1, true, 3.14)
fun fst pair =
match pair with
| (a, _) => a
fun swap pair =
match pair with
| (a, b) => (b, a)
Алгебраические типы данных
Объявляются через type TypeName = | Ctor1 | Ctor2 of field1 field2 | ….
Конструкторы с полями применяются как функции.
type Shape =
| Circle of radius
| Rectangle of width height
| Triangle of a b c
type Color =
| Red
| Green
| Blue
| Custom of r g b
let c = Circle 5.0
let r = Rectangle 4.0 6.0
let t = Triangle 3.0 4.0 5.0
fun area shape =
match shape with
| Circle r => pi * r * r
| Rectangle w h => w * h
| Triangle a b c =>
let s = (a + b + c) / 2.0 in
sqrt (s * (s - a) * (s - b) * (s - c))
Ленивые вычисления
Goat использует явную ленивость: вычисление откладывается только там, где это указано явно.
lazy / force
let expensive = lazy (factorial 100) -- thunk, не вычисляется
let result = force expensive -- вычисляется при force
let thunk = lazy (2 + 2)
let val = force thunk -- 4, мемоизируется
Бесконечные списки
Ленивые хвосты позволяют строить бесконечные структуры данных:
fun rec natsFrom n = n :: lazy (natsFrom (n + 1))
let nats = natsFrom 0
fun rec fibs a b = a :: lazy (fibs b (a + b))
let fibStream = fibs 0 1
fun rec repeat x = x :: lazy (repeat x)
-- Взять первые N элементов из бесконечного потока
let first10 = take 10 nats -- [0..9]
let fibs10 = take 10 fibStream -- [0, 1, 1, 2, 3, 5, ...]
Ленивый pipe ~>
Передаёт правый операнд как lazy-значение:
-- x ~> f эквивалентно f (lazy x)
let stream = fibs 0 1
let filtered = lazyFilter (\x -> x mod 2 == 0) stream
Pipe-операторы
Передают значение слева в функцию справа:
-- |> — обычный pipe
let result = [1..10]
|> filter (\x -> x mod 2 == 0)
|> map (\x -> x * x)
|> fold (\acc x -> acc + x) 0
|> show
-- "220" (4² + 6² + 8² + 10² = 220)
IO-блоки
IO-вычисления изолированы в блоках io { … }.
Внутри блока доступны специальные операторы:
| Конструкция | Смысл |
|---|---|
do! expr | Выполнить IO-действие, игнорировать результат |
let! x = expr | Выполнить IO-действие, связать результат с x |
let x = expr | Чистая привязка (без IO) |
return expr | Вернуть чистое значение из IO-блока |
fun main _ =
io {
do! println "Как вас зовут?"
let! name = input
let msg = "Привет, " ++ name ++ "!"
do! println msg
do! writeFile "log.txt" msg
}
Точка входа
Программа должна определять функцию main, принимающую один аргумент
(как правило _) и возвращающую IO-блок:
fun main _ =
io {
do! println "Hello, Goat!"
}
Образцы (patterns) — полный список
| Образец | Совпадает с | Пример |
|---|---|---|
_ | Любым значением (wildcard) | | _ => "other" |
| Литерал | Конкретным значением | | 0 => "zero" |
| Переменная | Любым; связывает значение | | x => x + 1 |
[] | Пустым списком | | [] => 0 |
p :: rest | Непустым списком; голова и хвост | | x :: xs => x |
[p1, p2] | Списком точно с N элементами | | [a, b] => a + b |
(p1, p2) | Tuple соответствующей длины | | (a, b) => a |
Ctor p1 p2 | Конструктором ADT | | Circle r => pi * r * r |
p as name | Образцом; также связывает всё значение | | x :: _ as xs => (x, xs) |
p when guard | Образцом и условием | | x when x > 0 => "pos" |
fun firstTwo xs =
match xs with
| [] => (0, 0)
| [x] => (x, 0)
| [x, y] => (x, y)
| x :: y :: _ => (x, y)
fun describeList xs =
match xs with
| [] => "empty"
| [_] => "singleton"
| _ :: _ as all when length all > 10 => "long"
| _ => "short"