Czym jest ML i dlaczego warto go poznać
ML (Meta Language) to rodzina języków programowania, która dała światu type inference, algebraic data types, pattern matching i moduły — czyli dokładnie te features, które dziś kochacie w Rust, TypeScript, Swift, Kotlin i dziesiątkach innych języków. Jeśli kiedykolwiek używaliście match, enum, Option<T> albo let x = ... — korzystacie z dziedzictwa ML.
ML nie jest jednym językiem — to cała rodzina: Standard ML (SML), OCaml, F#, a historycznie też Lazy ML i inne warianty. Ale „ML” jako koncepcja to coś więcej niż konkretna implementacja — to filozofia projektowania języków, która stawia na silne typy, wnioskowanie typów i deklaratywny styl programowania.
Dla każdego programisty, który chce zrozumieć, skąd wzięły się narzędzia, których używa na co dzień, ML jest lekturą obowiązkową. To jak poznawanie łaciny, żeby zrozumieć korzenie języków romańskich.
Krótka historia i ewolucja
ML narodził się w 1973 roku na University of Edinburgh, stworzony przez Robina Milnera i jego zespół. Początkowo nie był samodzielnym językiem — był meta-językiem do systemu dowodzenia twierdzeń LCF (Logic for Computable Functions). Stąd nazwa: Meta Language. ML służył do pisania taktyk dowodzenia, a nie do ogólnego programowania.
Ale szybko okazało się, że ML jest zbyt dobry, żeby być tylko narzędziem do meta-programowania. Jego system typów — z automatycznym wnioskowaniem typów (algorytm Hindleya-Milnera) — był rewolucyjny. Programiści zaczęli używać ML do pisania „zwykłych” programów, i tak narodził się język ogólnego przeznaczenia.
W 1983 roku Milner opublikował definicję Standard ML, a w 1990 roku ukazała się zrewidowana wersja (SML’90, potem SML’97), która stała się standardem. Równolegle, we Francji, Xavier Leroy i jego zespół rozwijali Caml, który ewoluował w OCaml — dziś najbardziej popularny dialekt ML. F# Microsoftu to kolejny potomek, przenoszący ML na platformę .NET.
Robin Milner otrzymał Nagrodę Turinga w 1991 roku, w dużej mierze za wkład w ML i system typów. To mówi wszystko o znaczeniu tego języka.
Kluczowe cechy i filozofia
Algorytm Hindleya-Milnera (HM) to serce ML. Pozwala na automatyczne wnioskowanie typów bez żadnych annotacji — kompilator sam figuring out, jakie typy mają wasze funkcje i wartości. Co więcej, HM gwarantuje znalezienie najbardziej ogólnego typu (principal type), co oznacza, że type inference jest kompletny i przewidywalny. Ten algorytm jest dziś używany w Haskellu, Elm, Rust (częściowo) i wielu innych językach.
Algebraic data types (ADTs) to sposób definiowania złożonych typów danych przez kombinację sum types (alternatywy) i product types (krotki/rekordy). W ML:
datatype 'a option = NONE | SOME of 'a
datatype 'a list = nil | :: of 'a * 'a list
To jest fundament modelowania danych w ML i jego potomkach. Każdy enum w Rust, każdy union type w TypeScript, każdy sealed class w Kotlin — to wszystko echa ADT z ML.
Pattern matching to naturalny towarzysz ADT. Zamiast if-else chains, ML pozwala na dekonstrukcję danych i dopasowanie do wzorców:
fun length nil = 0
| length (_ :: rest) = 1 + length rest
Kompilator ML sprawdza, czy pattern matching jest exhaustive (pokrywa wszystkie przypadki) i ostrzega o nieosiągalnych wzorcach. To jest standard w ML-owatych językach od dekad.
System modułów w Standard ML (szczególnie w rozszerzeniu SML Modules z funktorami) to jeden z najbardziej zaawansowanych systemów modularności w historii języków programowania. Sygnatury (interfejsy), struktury (implementacje) i funktory (moduły parametryzowane modułami) pozwalają na precyzyjną kontrolę nad abstrakcją i hermetyzacją.
Polimorfizm parametryczny — funkcje w ML są polimorficzne z automatu. fun id x = x ma typ 'a -> 'a, co oznacza, że działa dla dowolnego typu. Nie trzeba pisać generics ani type parameters — type inference to ogarnia.
Gdzie ML sprawdza się najlepiej
Historycznie ML był używany w badaniach nad kompilatorami, systemami dowodzenia twierdzeń i analizie programów. Dziś Standard ML jest rzadko używany w produkcji, ale jego potomkowie — OCaml i F# — mają realne zastosowania.
OCaml jest używany przez Jane Street (wielki fundusz tradingowy), Facebook (Flow, Infer, Hack), Docker i wiele innych firm. F# ma silną pozycję w fintech i na platformie .NET. Elm i ReasonML (dziś ReScript) przeniosły ML na frontend.
Ale najważniejszym „zastosowaniem” ML jest jego wpływ na inne języki. Rust w dużej mierze bazuje na ML-owatych koncepcjach — pattern matching, algebraic data types, type inference, system modułów. TypeScript dodał discriminated unions i narrowing. Swift ma enums z associated values. Kotlin ma sealed classes i when expressions. ML żyje wszędzie.
Jak wygląda kod w ML
Standard ML — klasyczne przykłady:
(* Definicja drzewa binarnego *)
datatype 'a tree = Leaf | Node of 'a tree * 'a * 'a tree
(* Wstawianie do BST *)
fun insert(x, Leaf) = Node(Leaf, x, Leaf)
| insert(x, Node(left, y, right)) =
if x < y then Node(insert(x, left), y, right)
else if x > y then Node(left, y, insert(x, right))
else Node(left, y, right)
(* In-order traversal *)
fun inorder Leaf = []
| inorder (Node(left, x, right)) =
inorder left @ [x] @ inorder right
A oto przykład z modułami (sygnatura i struktura):
signature STACK = sig
type 'a stack
val empty : 'a stack
val push : 'a * 'a stack -> 'a stack
val pop : 'a stack -> ('a * 'a stack) option
end
structure ListStack :> STACK = struct
type 'a stack = 'a list
val empty = []
fun push(x, s) = x :: s
fun pop [] = NONE
| pop (x :: rest) = SOME(x, rest)
end
Sygnatura STACK definiuje interfejs, ListStack go implementuje, a :> (opaque ascription) ukrywa implementację za interfejsem. To jest modularność na poziomie, którego Java czy C# wciąż nie osiągnęły.
Ekosystem i narzędzia
Dla Standard ML: SML/NJ (Standard ML of New Jersey) to najstarsza i najbardziej kompletna implementacja. MLton to whole-program compiler optymalizujący, generujący bardzo szybki kod. Poly/ML to implementacja z równoległością i wsparciem dla Isabelle (system dowodzenia twierdzeń).
Dla OCaml: ekosystem jest znacznie bogatszy — opam (menedżer pakietów), dune (build system), Merlin (IDE support), OCaml LSP (Language Server Protocol). Jane Street rozwija własne biblioteki (Core, Async) i narzędzia.
Dla F#: cały ekosystem .NET — NuGet, Visual Studio, Rider, Ionide. F# ma dostęp do tysięcy bibliotek .NET i jest wspierany przez Microsoft.
Społeczność ML jako całości jest rozproszona między dialektami, ale łączy się na konferencjach jak ICFP (International Conference on Functional Programming) i ML Workshop.
Mocne i słabe strony
ML dał światu fundamenty, na których buduje się większość nowoczesnych języków. Type inference, algebraic data types, pattern matching — to nie są niszowe koncepcje, to standard w dobrze zaprojektowanych językach. ML udowodnił, że silne typy i produktywność programisty nie są sprzeczne.
Standard ML ma jednak swoje problemy. Składnia bywa nieintuicyjna (szczególnie dla osób z backgroundem C-podobnym). System modułów, choć potężny, jest skomplikowany i trudny do opanowania. Brak efektów ubocznych w systemie typów (w przeciwieństwie do Haskella) oznacza, że ML nie gwarantuje czystości — co jedni uważają za pragmatyzm, inni za brak dyscypliny.
Największym problemem Standard ML jest to, że został wyprzedzony przez własne dzieci. OCaml i F# mają lepsze tooling, większe społeczności i więcej bibliotek. SML jest dziś głównie językiem akademickim i historycznym artefaktem.
Podsumowanie
ML to nie tylko język — to fundament, na którym stoi większość nowoczesnego programowania funkcyjnego i nie tylko. Każdy programista powinien chociaż raz napisać coś w SML albo OCaml, żeby zrozumieć, skąd wzięły się features, które kocha w swoim codziennym języku.
Jeśli chcecie zacząć z rodziną ML, OCaml jest najlepszym wyborem — łączy tradycję z nowoczesnym ekosystemem. F# jest idealny, jeśli pracujecie w .NET. A Standard ML? To klasyk, który warto znać, tak jak warto znać K&R C — nie do codziennej pracy, ale do zrozumienia korzeni.

