Примеры программ
Полные программы из каталога examples/ с подробными объяснениями.
Каждый пример демонстрирует отдельную группу возможностей языка.
Факториал — рекурсия и IO
Классический пример рекурсивной функции. Демонстрирует
fun rec, условие if/then/else и IO-блок.
fun rec factorial n =
if n <= 1 then 1
else n * factorial (n - 1)
fun main _ =
io {
do! println (show (factorial 0))
do! println (show (factorial 1))
do! println (show (factorial 5))
do! println (show (factorial 10))
}
Что здесь происходит
fun rec— объявляет рекурсивную функцию. Безrecимя функции недоступно внутри тела.- Базовый случай —
n <= 1возвращает1. - Рекурсивный случай —
n * factorial (n - 1). Скобки важны: без нихn - 1был бы отдельным применением. showконвертирует Int в String для вывода.do!выполняет IO-действие в монадическом блоке.
Вывод
1
1
120
3628800
Числа Фибоначчи — три подхода
Демонстрирует наивную рекурсию, хвостово-рекурсивную оптимизацию и бесконечный ленивый поток.
-- Наивная рекурсия: O(2^n)
fun rec fib n =
match n with
| 0 => 0
| 1 => 1
| n => fib (n - 1) + fib (n - 2)
-- Хвостовая рекурсия: O(n)
fun rec fibFast a b n =
if n == 0 then a
else fibFast b (a + b) (n - 1)
fun main _ =
io {
do! println "Naive fibonacci:"
do! println (show (map fib [0..9]))
do! println "Fast fibonacci:"
do! println (show (map (fibFast 0 1) [0..9]))
}
Бесконечный поток (из lazy_streams)
fun rec fibs a b = a :: lazy (fibs b (a + b))
let fibStream = fibs 0 1
take 10 fibStream -- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
a :: lazy (fibs b (a + b)) — голова списка вычисляется сразу,
хвост откладывается до вызова force (что делает take внутри).
Операции со списками
Демонстрирует базовые операции: сортировку, свёртку, отображение, фильтрацию и zip.
fun rec quicksort xs =
match xs with
| [] => []
| p :: rest =>
let smaller = filter (\x -> x < p) rest in
let greater = filter (\x -> x >= p) rest in
append (quicksort smaller) (p :: quicksort greater)
fun sumList xs = fold (\acc x -> acc + x) 0 xs
fun main _ =
io {
let nums = [5, 3, 8, 1, 9, 2, 7, 4, 6]
do! println (show (quicksort nums))
do! println (show (sumList [1..10]))
do! println (show (map (\x -> x * x) [1..5]))
do! println (show (filter (\x -> x mod 2 == 0) [1..10]))
do! println (show (zip [1..5] ["a", "b", "c", "d", "e"]))
}
Вывод
[1, 2, 3, 4, 5, 6, 7, 8, 9]
55
[1, 4, 9, 16, 25]
[2, 4, 6, 8, 10]
[(1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e")]
Сортировки — функции сравнения
Параметризованный quicksort: функция сравнения передаётся как аргумент, что позволяет сортировать в любом порядке и по любому критерию. Также mergesort для сравнения.
fun rec quicksort cmp xs =
match xs with
| [] => []
| p :: rest =>
let smaller = filter (\x -> cmp x p) rest in
let greater = filter (\x -> not (cmp x p) && x /= p) rest in
let equal = filter (\x -> x == p) rest in
append (quicksort cmp smaller)
(append (p :: equal) (quicksort cmp greater))
fun rec merge xs ys =
match (xs, ys) with
| ([], ys) => ys
| (xs, []) => xs
| (x :: xt, y :: yt) =>
if x <= y then x :: merge xt ys
else y :: merge xs yt
fun main _ =
io {
let nums = [64, 34, 25, 12, 22, 11, 90]
do! println (show (quicksort (\a b -> a < b) nums))
do! println (show (quicksort (\a b -> a > b) nums))
let strs = ["banana", "apple", "cherry"]
do! println (show (quicksort (\a b -> a < b) strs))
}
Ключевые идеи
- Функция
cmp— произвольный компаратор, передаётся как аргумент (функция высшего порядка). - Одна реализация покрывает сортировку по возрастанию, убыванию и произвольному критерию.
- Tuple-паттерн
(xs, ys)вmergeпозволяет сопоставлять несколько значений одновременно.
Ленивые потоки — бесконечные последовательности
Демонстрирует явную ленивость: бесконечные потоки, решето Эратосфена и степени двойки.
-- Решето Эратосфена
fun rec sieve xs =
match xs with
| [] => []
| p :: rest =>
p :: lazy (sieve (lazyFilter (\x -> x mod p /= 0) (force rest)))
let primes = sieve (natsFrom 2)
-- Числа Фибоначчи как поток
fun rec fibs a b = a :: lazy (fibs b (a + b))
let fibStream = fibs 0 1
-- Степени числа
fun rec powersOf base n = n :: lazy (powersOf base (n * base))
let twoPowers = powersOf 2 1
fun main _ =
io {
do! println (show (take 10 primes))
do! println (show (take 10 fibStream))
do! println (show (take 10 twoPowers))
do! println (show (take 5 (drop 100 nats)))
}
Разбор решета Эратосфена
natsFrom 2— бесконечный поток 2, 3, 4, 5, …sieveберёт голову (простое число), форсирует хвост, фильтрует кратные и рекурсивно просеивает — всё лениво.force restявно форсирует ленивый хвост перед передачей вlazyFilter.lazyFilterснова создаёт ленивый хвост, поэтому решето работает по требованию.
Вывод
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
[100, 101, 102, 103, 104]
Option-монада — безопасные вычисления
Показывает, как использовать Option для безопасных вычислений
без исключений. Функции-цепочки через bindOption.
fun safeDivide x y =
if y == 0 then None
else Some (x / y)
fun safeHead xs =
match xs with
| [] => None
| x :: _ => Some x
fun safeSqrt x =
if x < 0 then None
else Some (sqrt (toFloat x))
-- Цепочка safe-операций через bindOption
fun pipeline x y =
bindOption (safeDivide 100 x) (\q ->
bindOption (safeSqrt q) (\r ->
bindOption (safeDivide (toInt r) y) (\result ->
Some result)))
fun main _ =
io {
do! println (show (pipeline 4 2)) -- Some 2
do! println (show (pipeline 0 2)) -- None
}
Как работает цепочка
bindOption opt f работает так: если opt равен None —
немедленно возвращает None, не вызывая f. Если Some x —
передаёт x в f. Это позволяет строить цепочки безопасных
вычислений: первый же None «замыкает» цепочку.
Алгебраические типы данных — фигуры
Показывает объявление ADT, конструкторы с несколькими полями и исчерпывающий pattern matching для вычисления площади и периметра.
type Shape =
| Circle of radius
| Rectangle of width height
| Triangle of a b c
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))
fun perimeter shape =
match shape with
| Circle r => 2.0 * pi * r
| Rectangle w h => 2.0 * (w + h)
| Triangle a b c => a + b + c
fun main _ =
io {
let shapes = [Circle 5.0, Rectangle 4.0 6.0, Triangle 3.0 4.0 5.0]
do! println (unlines (map describeShape shapes))
let largest = fold (\acc s -> if area s > area acc then s else acc)
(head shapes) (tail shapes)
do! println ("Largest: " ++ showShape largest)
}
Вывод
Circle(r=5.0) area=79 perim=31
Rect(4.0x6.0) area=24 perim=20
Triangle(3.0,4.0,5.0) area=6 perim=12
Largest: Circle(r=5.0)
Работа с файлами
Демонстрирует запись, чтение и обработку файлового содержимого.
fun main _ =
io {
let path = "examples/tmp_output.txt"
do! writeFile path "Hello from Goat!\nLine 2\nLine 3\n"
let! content = readFile path
do! println "File contents:"
do! println content
let lineCount = length (lines content)
do! println ("Lines: " ++ show lineCount)
let wordList = words content
do! println ("Words: " ++ show (length wordList))
}
Разница do! и let!
| Конструкция | Использование |
|---|---|
do! action |
Выполнить IO-действие ради побочного эффекта (вывод, запись файла). Результат игнорируется. |
let! x = action |
Выполнить IO-действие и связать результат с именем x (чтение файла, ввод). |
let x = expr |
Чистая привязка внутри IO-блока — без выполнения IO. |