OCaml
Экспресс-тур по OCaml: типы, функции, сопоставление с образцом, списки, записи, модули и императивные элементы — весь язык на одной странице.
OCaml — функциональный язык со строгой статической типизацией и выводом типов. Значения по умолчанию неизменяемы, всё — выражение, а компилятор ловит большинство ошибок до запуска. Ниже — весь язык на одной странице через комментарии в коде.
Вывод и комментарии
(* Однострочных комментариев в OCaml нет:
ВСЕ комментарии оформляются как (* ... *) и могут вкладываться (* вот так *). *)
print_string "Привет\n" (* печать строки как есть *)
print_int 42; print_newline () (* печать числа + перевод строки *)
print_endline "строка + \n" (* печать строки с переводом строки *)
(* Printf даёт форматированный вывод (как в C): *)
Printf.printf "%s, тебе %d лет\n" "Аня" 20 (* => Аня, тебе 20 лет *)
Printf.printf "%.2f\n" 3.14159 (* => 3.14 *)
Printf.printf "%b %c\n" true 'x' (* => true x *)
(* ; разделяет выражения типа unit (побочные эффекты идут друг за другом) *)
Значения и типы
Имя связывается со значением через let. Связывание неизменяемо: повторный let создаёт новое значение, а не меняет старое.
let x = 10 (* int — целое *)
let pi = 3.14 (* float — число с плавающей точкой *)
let flag = true (* bool — true | false *)
let ch = 'A' (* char — один символ в одинарных кавычках *)
let name = "OCaml" (* string — строка в двойных кавычках *)
let nothing = () (* unit — единственное значение (), аналог "ничего" *)
(* Типы выводятся автоматически, но их можно указать явно: *)
let year : int = 2026
(* Связывание неизменяемо. "Переопределение" — это новое имя (shadowing): *)
let x = 10
let x = x + 5 (* => 15: создан новый x, старый недоступен *)
(* Локальное связывание через let ... in: *)
let area =
let r = 5 in
let s = float_of_int r in
pi *. s *. s (* => 78.5 *)
(* Преобразования типов ЯВНЫЕ — неявных нет: *)
let f = float_of_int 7 (* int -> float: 7. *)
let i = int_of_float 3.9 (* float -> int: 3 (отбрасывает дробь) *)
let s = string_of_int 99 (* int -> string: "99" *)
Функции
Функция определяется тем же let. Аргументы пишутся через пробел, тип возврата выводится. Каждая функция возвращает значение последнего выражения.
let square x = x * x (* square : int -> int *)
let add a b = a + b (* add : int -> int -> int *)
square 5 (* => 25 *)
add 2 3 (* => 5: вызов — имя и аргументы через пробел, без скобок *)
(* Частичное применение: применили не все аргументы — получили функцию *)
let add10 = add 10 (* add10 : int -> int *)
add10 7 (* => 17 *)
(* Анонимные функции (лямбды) через fun: *)
let inc = fun x -> x + 1
inc 41 (* => 42 *)
(fun a b -> a * b) 6 7 (* => 42 *)
(* Рекурсия требует let rec: *)
let rec fact n =
if n <= 1 then 1
else n * fact (n - 1)
fact 5 (* => 120 *)
(* Взаимная рекурсия через let rec ... and ...: *)
let rec is_even n = if n = 0 then true else is_odd (n - 1)
and is_odd n = if n = 0 then false else is_even (n - 1)
Операторы
Целочисленная и вещественная арифметика РАЗДЕЛЕНЫ: для float операторы с точкой (+., -., *., /.).
(* Целые: + - * / mod *)
7 + 2 (* => 9 *)
7 / 2 (* => 3 (целочисленное деление) *)
7 mod 2 (* => 1 (остаток) *)
(* Вещественные — операторы С ТОЧКОЙ: *)
3.0 +. 1.5 (* => 4.5 *)
10.0 /. 4.0 (* => 2.5 *)
2.0 ** 10.0 (* => 1024. (возведение в степень для float) *)
(* Нельзя смешивать: 1 + 2.0 — ошибка типов. Нужно явное приведение. *)
(* Сравнения работают для любых типов и дают bool: *)
3 = 3 (* => true (= это сравнение значений, НЕ присваивание) *)
3 <> 4 (* => true (<> это "не равно") *)
3 < 5 (* => true *)
"a" < "b" (* => true (строки сравниваются лексикографически) *)
(* Логические: && || not *)
true && false (* => false *)
not true (* => false *)
(* Конкатенация строк через ^ : *)
"Hello, " ^ "world" (* => "Hello, world" *)
(* Оператор конвейера |> передаёт значение слева в функцию справа: *)
5 |> square |> string_of_int (* => "25" (то же, что string_of_int (square 5)) *)
Условия
(* if/then/else — ВЫРАЖЕНИЕ, возвращает значение. Ветки одного типа. *)
let sign n =
if n > 0 then "плюс"
else if n < 0 then "минус"
else "ноль"
sign (-3) (* => "минус" *)
(* if без else допустим только когда then имеет тип unit: *)
if true then print_endline "да"
(* Часто условия выражают через match (см. ниже) — это идиоматичнее: *)
let describe n =
match n with
| 0 -> "ноль"
| 1 -> "один"
| _ -> "много" (* _ — образец "что угодно остальное" *)
describe 1 (* => "один" *)
Списки
Список — неизменяемая односвязная цепочка элементов ОДНОГО типа.
let nums = [1; 2; 3; 4] (* элементы через ; — тип int list *)
let empty = [] (* пустой список *)
(* :: ("cons") добавляет элемент В НАЧАЛО (создаёт новый список): *)
0 :: nums (* => [0; 1; 2; 3; 4] *)
(* @ конкатенирует два списка: *)
[1; 2] @ [3; 4] (* => [1; 2; 3; 4] *)
List.hd nums (* => 1 (голова) *)
List.tl nums (* => [2; 3; 4] (хвост) *)
List.length nums (* => 4 *)
(* map — применить функцию к каждому элементу: *)
List.map (fun x -> x * x) nums (* => [1; 4; 9; 16] *)
(* filter — оставить подходящие: *)
List.filter (fun x -> x mod 2 = 0) nums (* => [2; 4] *)
(* fold_left — свёртка с аккумулятором (здесь сумма): *)
List.fold_left (fun acc x -> acc + x) 0 nums (* => 10 *)
(* Список можно разбирать через match по образцам [] и hd :: tl: *)
let rec sum lst =
match lst with
| [] -> 0
| head :: tail -> head + sum tail
sum nums (* => 10 *)
Кортежи и записи
(* Кортеж (tuple) — фиксированный набор значений РАЗНЫХ типов: *)
let point = (3, 4) (* тип int * int *)
let person = ("Аня", 20, true) (* string * int * bool *)
let (a, b) = point (* деструктуризация: a=3, b=4 *)
fst point (* => 3 (только для пары) *)
snd point (* => 4 *)
(* Запись (record) — именованные поля. Сначала объявляем тип: *)
type user = { name : string; age : int }
let u = { name = "Боб"; age = 30 }
u.name (* => "Боб" (доступ к полю через точку) *)
u.age (* => 30 *)
(* Функциональное обновление: новая запись на основе старой: *)
let older = { u with age = u.age + 1 } (* { name = "Боб"; age = 31 } *)
(* u не изменился — записи неизменяемы *)
Алгебраические типы
Через type описывают перечисления и составные типы. Вариант может нести данные.
(* Простой вариантный тип (перечисление): *)
type color = Red | Green | Blue
let c = Green
(* Варианты с данными: *)
type shape =
| Circle of float (* радиус *)
| Rect of float * float (* ширина, высота *)
let area s =
match s with
| Circle r -> 3.14 *. r *. r
| Rect (w, h) -> w *. h
area (Circle 2.0) (* => 12.56 *)
area (Rect (3.0, 4.0)) (* => 12. *)
(* Option — встроенный тип для "значение или ничего" (вместо null): *)
(* type 'a option = None | Some of 'a *)
let safe_div a b = if b = 0 then None else Some (a / b)
safe_div 10 2 (* => Some 5 *)
safe_div 10 0 (* => None *)
(* Рекурсивный тип — например, двоичное дерево: *)
type tree =
| Leaf
| Node of tree * int * tree
let t = Node (Leaf, 5, Node (Leaf, 8, Leaf))
let rec count = function (* function — короткий match по единственному аргументу *)
| Leaf -> 0
| Node (l, _, r) -> 1 + count l + count r
count t (* => 2 *)
Сопоставление с образцом
match — главный инструмент ветвления: разбирает значение по структуре. Компилятор предупреждает о незакрытых случаях.
(* Деструктуризация прямо в образце: *)
let describe_point p =
match p with
| (0, 0) -> "начало координат"
| (x, 0) -> Printf.sprintf "на оси X в %d" x
| (0, y) -> Printf.sprintf "на оси Y в %d" y
| (x, y) -> Printf.sprintf "точка (%d, %d)" x y
describe_point (5, 0) (* => "на оси X в 5" *)
(* Охранные условия when уточняют образец: *)
let classify n =
match n with
| 0 -> "ноль"
| x when x > 0 -> "положительное"
| _ -> "отрицательное"
classify (-7) (* => "отрицательное" *)
(* Объединение образцов через | : *)
let is_vowel c =
match c with
| 'a' | 'e' | 'i' | 'o' | 'u' -> true
| _ -> false
is_vowel 'e' (* => true *)
(* Работа с Option через match: *)
let unwrap_or default opt =
match opt with
| Some v -> v
| None -> default
unwrap_or 0 (Some 42) (* => 42 *)
unwrap_or 0 None (* => 0 *)
Модули
Модуль группирует типы и функции в пространство имён. Сигнатура задаёт интерфейс.
module Stack = struct
type 'a t = 'a list
let empty = []
let push x s = x :: s
let pop s =
match s with
| [] -> None
| x :: rest -> Some (x, rest)
end
(* Обращение к содержимому через имя модуля и точку: *)
let s = Stack.push 1 Stack.empty
let s = Stack.push 2 s
Stack.pop s (* => Some (2, [1]) *)
(* Стандартная библиотека — это тоже модули: List, String, Array, Map ... *)
String.length "hi" (* => 2 *)
String.uppercase_ascii "ok" (* => "OK" *)
(* Сигнатура (интерфейс) ограничивает то, что видно снаружи: *)
module type COUNTER = sig
val next : unit -> int (* val объявляет имя и его тип *)
end
(* Локально открыть модуль, чтобы не писать префикс: *)
let n = List.(length (map succ [1; 2; 3])) (* => 3 *)
Императивные элементы
OCaml позволяет изменяемое состояние, когда оно нужно: ссылки, массивы, циклы.
(* ref — изменяемая "ячейка". ! читает, := записывает: *)
let counter = ref 0
counter := !counter + 1
counter := !counter + 1
!counter (* => 2 *)
(* Массив — изменяемый, индексация с 0 через .(): *)
let arr = [| 10; 20; 30 |] (* литерал массива в [| ... |] *)
arr.(0) (* => 10 *)
arr.(1) <- 99 (* запись в элемент *)
arr (* => [|10; 99; 30|] *)
Array.length arr (* => 3 *)
(* Цикл for (тело имеет тип unit, выполняется ради эффекта): *)
let total = ref 0
for i = 1 to 5 do
total := !total + i
done;
!total (* => 15 *)
(* Цикл while: *)
let n = ref 3
while !n > 0 do
print_int !n;
n := !n - 1
done (* печатает 321 *)
(* Поля записи можно сделать изменяемыми через mutable: *)
type account = { mutable balance : int }
let acc = { balance = 100 }
acc.balance <- 150
acc.balance (* => 150 *)
Полезное
Каррирование и частичное применение — повседневный инструмент OCaml.
(* Все функции каррированы: add : int -> int -> int это
на самом деле int -> (int -> int). Поэтому частичное применение бесплатно. *)
let add a b = a + b
let inc = add 1 (* зафиксировали первый аргумент *)
List.map inc [1; 2; 3] (* => [2; 3; 4] *)
(* Частичное применение + конвейер дают чистый код без временных переменных: *)
let result =
[1; 2; 3; 4; 5; 6]
|> List.filter (fun x -> x mod 2 = 0) (* [2; 4; 6] *)
|> List.map (fun x -> x * 10) (* [20; 40; 60] *)
|> List.fold_left (+) 0 (* => 120 — (+) это функция-оператор *)
(* Композиция через явную лямбду: *)
let compose f g = fun x -> f (g x)
let add1_then_double = compose (fun x -> x * 2) (fun x -> x + 1)
add1_then_double 5 (* => 12 *)
(* ignore отбрасывает результат, когда важен только эффект: *)
ignore (square 99)
(* Дальше: модуль Map/Set, исключения (try ... with), функторы,
полиморфизм 'a, метки аргументов ~name — но основа языка уже здесь. *)