LEARN X · ЗА 17 МИН

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 — но основа языка уже здесь. *)
Поддержать проект