Obsah
3. Inicializace prostředí přes správce opam
4. Instalace interaktivního prostředí utop
5. Přímá interakce s interpretrem jazyka OCaml
6. Využití interaktivního prostředí utop
7. Základní základy programovacího jazyka OCaml
9. Jak spustit program typu „Hello, world!“?
13. Explicitní specifikace návratového typu funkcí
16. Repositář s demonstračními příklady
1. Programovací jazyk OCaml
„OCaml: the rehabilitation clinic for OO programmers.“
Erik Meijer
Na úvodní článek o programovacím jazyku F# dnes částečně navážeme, protože se seznámíme se základy jazyka OCaml, jenž je považován za předka jazyka F# (a současně za jazyk, kterého F# v některých ohledech nikdy nepředběhl). Samotný jazyk OCaml se vyvinul z programovacího jazyka nazvaného Caml, přičemž předchůdci OCamlu se jmenovali Caml light a Caml special light. Po přidání podpory pro objektově orientované programování vznikl jazyk se jménem Objective Caml neboli zkráceně OCaml (podrobnější historie byla zmíněna právě v článku o F#).
Obrázek 1: Logo jazyka OCaml. Jak jsme si již řekli v článku o F#, pokud zvíře míří doprava, jedná se o jazyk OCaml, pokud doleva, jde o Perl.
V dnešním článku si nejdříve řekneme, jak se OCaml nainstaluje, jak se nastaví jeho interaktivní prostředí utop a taktéž se seznámíme se základy tohoto zajímavého a stále poněkud přehlíženého programovacího jazyka, včetně jedné významné odlišnosti oproti jazyku F# (jedná se o striktní oddělení operátorů pro jednotlivé datové typy).
2. Instalace balíčku opam
V navazujícím textu si řekneme, jakým způsobem je možné si nainstalovat nástroje nutné pro psaní aplikací v programovacím jazyku OCaml (ve skutečnosti si postupně doinstalujeme prakticky celý ekosystém OCamlu). Většinu potřebných základních balíčků pro vývoj v OCamlu většinou nalezneme přímo v repositářích distribuce Linuxu. Vhodné je nainstalovat si přímo balíček nazvaný opam, což je správce prostředí pro OCaml (tedy přibližně obdoba pip+virtualenv). Příkladem může být instalace balíčku opam na Ubuntu či Mintu:
$ sudo apt-get install opam
Povšimněte si, že se skutečně nejedná o minimalistickou instalaci, protože ekosystém OCamlu společně se závislostmi (mj. i C++) vyžaduje přibližně půl gigabajtu diskového prostoru:
Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: build-essential cpp-9 darcs g++ g++-9 gcc-9 gcc-9-base ledit libamd2 libasan5 libgcc-9-dev libglpk40 libncurses-dev libncurses5-dev libncurses6 libncursesw6 libstdc++-9-dev libtinfo6 mercurial mercurial-common ocaml ocaml-base ocaml-base-nox ocaml-compiler-libs ocaml-interp ocaml-man ocaml-nox opam-doc opam-installer Suggested packages: gcc-9-locales g++-multilib g++-9-multilib gcc-9-doc gcc-9-multilib libiodbc2-dev default-libmysqlclient-dev ncurses-doc libstdc++-9-doc kdiff3 | kdiff3-qt | kompare | meld | tkcvs | mgdiff qct python-mysqldb python-openssl python-pygments wish ocaml-doc tuareg-mode The following NEW packages will be installed: build-essential darcs g++ g++-9 ledit libamd2 libglpk40 libncurses-dev libncurses5-dev libstdc++-9-dev mercurial mercurial-common ocaml ocaml-base ocaml-base-nox ocaml-compiler-libs ocaml-interp ocaml-man ocaml-nox opam opam-doc opam-installer The following packages will be upgraded: cpp-9 gcc-9 gcc-9-base libasan5 libgcc-9-dev libncurses6 libncursesw6 libtinfo6 8 upgraded, 22 newly installed, 0 to remove and 362 not upgraded. Need to get 127 MB of archives. After this operation, 478 MB of additional disk space will be used.
Po (doufejme, že úspěšném) dokončení instalace si vyzkoušejte, zda je možné spustit příkaz opam:
$ opam
Měla by se vypsat tato nápověda:
usage: opam [--version] [--help] <command> [<args>] The most commonly used opam commands are: init Initialize opam state, or set init options. list Display the list of available packages. show Display information about specific packages. install Install a list of packages. remove Remove a list of packages. update Update the list of available packages. upgrade Upgrade the installed package to latest version. config Display configuration options for packages. repository Manage opam repositories. switch Manage multiple installation prefixes. pin Pin a given package to a specific version or source. admin Tools for repository administrators See 'opam help <command>' for more information on a specific command.
3. Inicializace prostředí přes správce opam
V dalším kroku provedeme inicializaci prostředí využívaného správcem opam. Jedná se o operaci, kterou je nutné provést pouze jedenkrát, takže i když se jedná o poměrně pomalou operaci (cca jedna až dvě minuty v závislosti na rychlosti připojení), nebude nutné tuto operaci opakovat:
$ opam init
Povšimněte si, že se v rámci inicializace stahují balíčky jazyka OCaml:
[NOTE] Will configure from built-in defaults. Checking for available remotes: rsync and local, git, mercurial, darcs. Perfect! <><> Fetching repository information ><><><><><><><><><><><><><><><><><><><><><> [default] Initialised A hook can be added to opam's init scripts to ensure that the shell remains in sync with the opam environment when they are loaded. Set that up? [y/N] n <><> Creating initial switch (ocaml-system>=4.02.3) <><><><><><><><><><><><><><> <><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><> <><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><> ∗ installed base-bigarray.base ∗ installed base-threads.base ∗ installed base-unix.base ∗ installed ocaml-system.4.08.1 ∗ installed ocaml-config.1 ∗ installed ocaml.4.08.1 Done. # Run eval $(opam env) to update the current shell environment
4. Instalace interaktivního prostředí utop
Již v rámci předchozího kroku se nainstalovaly všechny nástroje nutné pro vývoj v jazyku OCaml, tedy jak interpret, tak i překladač do nativního kódu společně s překladačem do bajtkódu. Ovšem v praxi je výhodnější namísto standardního interpretru jazyka OCaml použít interaktivní prostředí nazvané utop. To již můžeme nainstalovat přímo přes správce balíčků opam, a to konkrétně následujícím způsobem:
$ opam install utop
Společně s utop se nainstaluje i celá řada dalších nástrojů a balíčků. S některými z nich (například s dune) se ještě později setkáme:
The following actions will be performed: ∗ install dune 3.10.0 [required by utop] ∗ install ocamlfind 1.9.6 [required by utop] ∗ install ocamlbuild 0.14.2 [required by logs] ∗ install trie 1.0.0 [required by mew] ∗ install result 1.5 [required by zed] ∗ install csexp 1.5.2 [required by dune-configurator] ∗ install cppo 1.6.9 [required by utop] ∗ install base-bytes base [required by ocplib-endian] ∗ install uchar 0.0.2 [required by zed] ∗ install topkg 1.0.7 [required by logs] ∗ install mew 0.1.0 [required by mew_vi] ∗ install dune-configurator 3.10.0 [required by lwt] ∗ install ocplib-endian 1.2 [required by lwt] ∗ install uutf 1.0.3 [required by zed] ∗ install react 1.2.2 [required by utop] ∗ install lwt 5.7.0 [required by utop] ∗ install uucp 15.0.0 [required by zed] ∗ install mew_vi 0.5.0 [required by lambda-term] ∗ install lwt_react 1.2.0 [required by utop] ∗ install logs 0.7.0 [required by utop] ∗ install uuseg 15.0.0 [required by zed] ∗ install zed 3.2.1 [required by utop] ∗ install lambda-term 3.3.2 [required by utop] ∗ install utop 2.12.1 ===== ∗ 24 ===== ∗ installed dune.3.10.0 ∗ installed csexp.1.5.2 ∗ installed cppo.1.6.9 ∗ installed result.1.5 ∗ installed trie.1.0.0 ∗ installed ocplib-endian.1.2 ∗ installed uuseg.15.0.0 ∗ installed mew.0.1.0 ∗ installed zed.3.2.1 ∗ installed mew_vi.0.5.0 ∗ installed dune-configurator.3.10.0 ∗ installed lwt.5.7.0 ∗ installed lwt_react.1.2.0 ∗ installed logs.0.7.0 ∗ installed lambda-term.3.3.2 ∗ installed utop.2.12.1 Done.
5. Přímá interakce s interpretrem jazyka OCaml
Po instalaci OCamlu a nastavení jeho prostředí si můžeme spustit interpret tohoto programovacího jazyka. Je to snadné, protože postačuje použít následující příkaz:
$ ocaml
Na terminál by se měla vypsat aktuálně nainstalovaná verze, konkrétně:
OCaml version 4.08.1
Znak # znamená výzvu (prompt), což znamená, že za tento znak můžeme zadávat jednotlivé příkazy (což jsou prakticky vždy výrazy), interpret je zpracuje, vyhodnotí a vypíše jejich výsledek. Pokusme se například o součet hodnot 1+1:
# 1+1;; - : int = 2
Povšimněte si zejména toho, že výsledkem není pouze vypsaná hodnota, ale současně i její typ, a to vždy. V tomto ohledu je jazyk OCaml (resp. přesněji řečeno jeho interpret) velmi důsledný.
6. Využití interaktivního prostředí utop
V rámci čtvrté kapitoly jsme si nainstalovali nástroj nazvaný utop, jehož použití je v naprosté většině případů příjemnější, než konverzace se standardním interpretrem jazyka OCaml. utop totiž používá zvýraznění jednotlivých typů zpráv, nabízí plnohodnotnou editaci na příkazovém řádku, zobrazuje seznam modulů atd. Nástroj utop se spustí jednoduše:
$ utop
Podívejme se nyní na to, jak vypadá jeho prostředí:
Obrázek 2: Prostředí utop po svém spuštění. V horní části terminálu jsou zobrazeny uvítací zprávy, v prostřední části vidíme vstupní textové pole (víceřádkové) a na spodním okraji se zobrazuje seznam modulů (a po zápisu jména modulu se zobrazí jména funkcí a dalších symbolů z daného modulu).
Obrázek 3: Definice funkce a zavolání této funkce v prostředí utop.
7. Základní základy programovacího jazyka OCaml
Podobně jako F#, patří i OCaml do rodiny funkcionálních programovacích jazyků, konkrétně do skupiny se silným typovým systémem (na rozdíl od LISPu či Clojure) a s podporou typové inference. Při popisu OCamlu se tedy budeme snažit soustředit se právě na tyto dva prvky jazyka – na způsob práce s funkcemi a práce s datovými typy. Dnes se seznámíme s těmi prvky OCamlu, které jsme si (v případě jazyka F#) popsali v již zmíněném úvodním článku. Ukážeme si tedy způsob definice funkce, odvození typu funkce a v neposlední řadě taktéž specifický přístup jazyka OCaml k rozeznání operací podle použitých operátorů.
8. Tisk hodnot na terminál
Pro tisk nějaké hodnoty či hodnot na terminál nabízí programovací jazyk OCaml funkce, v jejichž názvu se již tradičně – ostatně podobně jako v naprosté většině ostatních programovacích jazyků – vyskytuje slovo print. Některé z těchto funkcí jsou umístěny ve výchozím modulu; jedná se například o funkci print_string, print_int atd. Program typu „Hello, world!“ by tedy mohl v OCamlu vypadat následovně:
print_string "Hello, world!"
Další podobně koncipované funkce nalezneme v modulu nazvaném Printf. Jedná se zejména o funkci printf, která nabízí formátovaný výstup (známe z C, Javy atd. atd.). Vzhledem k tomu, že „Hello, world!“ neobsahuje žádný formátovací znak, můžeme psát:
Printf.printf "Hello, world!"
9. Jak spustit program typu „Hello, world!“?
Prográmky ukázané v předchozí kapitole lze spustit několika způsoby. Ukažme si některé základní způsoby, k dalším metodám se vrátíme později:
- Spustíme interpret příkazem ocaml a překopírujeme do něj zdrojový kód
- Spustíme interaktivní prostředí utop a překopírujeme do něj zdrojový kód
- Spustíme interpret, ovšem se zadáním skriptu, který se má použít: ocaml hello_world1.ml
- Dtto v případě prostředí utop: utop -init hello_worlld.ml
- V interpretru i interaktivním prostředí můžeme použít příkaz #use „hello_world.ml“
- A v neposlední řadě je možné program přeložit do nativního kódu příkazem ocamlc hello_world1 (k tomu se ještě vrátíme)
10. Definice funkce
Jazyk OCaml patří do rodiny funkcionálních programovacích jazyků, jak již to ostatně částečně prozrazuje jeho jméno. A ve funkcionálních programovacích jazycích mají funkce stejně plnohodnotný význam, jako jakékoli jiné hodnoty. V jazyku OCaml je neanonymní funkce vytvořena stejně jako jakákoli jiná hodnota s využitím klíčového slova let (zatímco u jazyka ML se používalo klíčové slovo fun). Vyzkoušíme si to na definici funkce pojmenované inc, která bude mít jediný parametr nazvaný x. V těle funkce se vypočte výraz x+1, jehož výsledek je současně i návratovou hodnotou funkce:
let inc x = x + 1;;
U této funkce je zajímavé především to, že jsme nikde neuvedli typ parametru ani typ návratové hodnoty a přesto jazyk OCaml korektně zjistil, že se jedná o funkci int → int (tedy jediným parametrem je celé číslo a návratovou hodnotou je taktéž celé číslo). Přitom se vychází z těla funkce, tedy z výrazu n+1, což je v jazyku OCaml striktně součet dvou celých čísel (zde navíc mnohem striktnější, než například v F#, kde jsou operátory přetížené).
11. Zavolání funkce
Podívejme se nyní na způsob zavolání již definované funkce. Při samotném volání funkce se parametry nemusí psát do závorek, ani se nemusí oddělovat čárkami:
let inc x = x+1;; let y = inc 10;;
Ovšem pokud se funkce volá v pozici, že její výsledek je parametrem jiné funkce, závorkám se nevyhneme. Ovšem píšou se okolo celého vyhodnocovaného parametru – podvýrazu:
let inc x = x+1;; print_int (inc 10);;
Alternativní způsob, kdy voláme funkci Prinft.printf s dvojicí parametrů, by mohl vypadat takto:
let inc x = x+1;; Printf.printf "%d" (inc 10);;
12. Anonymní funkce
V jazyku OCaml taktéž existuje klíčové slovo fun, ovšem na rozdíl od jazyka ML (ze kterého OCaml vychází) se používá pro definici anonymních funkcí (známé lambda výrazy):
fun x -> x + 1;;
Předchozí řádek zapsaný uživatelem vytvořil anonymní funkci (bez jména) a navázal ji na speciální symbol it, jenž vždy (v interaktivním prostředí) obsahuje výsledek posledního výrazu. To znamená, že anonymní funkci můžeme zavolat (a ihned poté ztratit odkaz na ni).
Ve skutečnosti je definice klasické funkce ukázaná v předchozích kapitolách jen syntaktickým cukrem k plnému zápisu:
let inc = fun x -> x+1;; Printf.printf "%d" (inc 2);;
nebo:
let add = fun x y -> x+y;; Printf.printf "%d" (add 1 2);;
Což je vlastně ekvivalentní zápisu anonymní funkce v JavaScriptu s přiřazením výsledku do konstanty:
const add = function (x, y) { return x + y; };
nebo možná ještě lépe (stále se nacházíme v peklu zvaném JavaScript):
let add = (x, y) => x + y;
13. Explicitní specifikace návratového typu u funkcí
U funkce inc v původním tvaru:
let inc x = x + 1;;
se typ jejího argumentu a taktéž i typ výsledné hodnoty odvodí na základě použitého operátoru.
V případě potřeby je pochopitelně možné explicitně specifikovat typ argumentu či argumentů funkce, popř. můžeme zapsat typ návratové hodnoty. Syntaxe je v tomto případě jednoduchá – za jméno argumentu (či argumentů) se zapíše dvojtečka a za ní požadovaný datový typ, přičemž argument i typ musí být v závorce:
let inc (x:int) = x+1;;
Typ návratové hodnoty se píše pouze za dvojtečku; nyní již bez závorek. Například můžeme specifikovat, že typ argumentu x má být explicitně celočíselná hodnota:
let inc x:int = x + 1;;
Na rozdíl od programovacího jazyka F# však nemůžeme specifikovat typ argumentu jako float, protože se provádí celočíselné sčítání (operátor + není v OCamlu přetížený):
let inc x:float = x + 1;;
Zjištěná chyba ve výrazu x + 1:
Error: This expression has type int but an expression was expected of type float
14. Nepřetížené operátory
V jazyku OCaml (ale nikoli už v F#) se striktně rozlišuje mezi celočíselnými operátory a operátory pro hodnoty typu float (což dále zpřísňuje typovou kontrolu). Konkrétně to znamená, že pro typ float je za všemi základními aritmetickými operátory zapsaná tečka. Následující příklad je tedy nekorektní a OCaml (interpret i překladač) na chybu upozorní:
let inc x:float = x+1.0;; Printf.printf "%f" (inc 2.0);;
Korektní zápis vypadá následovně (povšimněte si, že voláme operátor +. a nikoli jen +):
let inc x:float = x+.1.0;; Printf.printf "%f" (inc 2.0);;
15. Lokální symboly
Na závěr dnešního článku se zmiňme ještě o jednom rozdílu mezi jazyky OCaml a F#. Oba jazyky umožňují definice lokálních symbolů (a tedy i prostředí navázaného na tyto symboly), ovšem OCaml vyžaduje použití klíčového slova in (v F# je situace složitější, protože tento jazyk umožňuje přepínání mezi více syntaxemi). Použití lokálního symbolu x při výpočtu hodnoty proměnné answer tedy může vypadat následovně:
let answer = let x = 6 in x*7;; Printf.printf "anwser=%d" answer
Podobně lze vytvořit více lokálních symbolů:
let answer = let x = 6 in let y = 7 in x*y ;; Printf.printf "anwser=%d" answer
16. Repositář s demonstračními příklady
Všechny výše popsané demonstrační příklady byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/ocaml-examples/. V tabulce umístěné pod tímto odstavcem jsou uvedeny odkazy na tyto příklady:
17. Odkazy na Internetu
- General-Purpose, Industrial-Strength, Expressive, and Safe
https://ocaml.org/ - OCaml playground
https://ocaml.org/play - Online Ocaml Compiler IDE
https://www.jdoodle.com/compile-ocaml-online/ - Get Started – OCaml
https://www.ocaml.org/docs - Get Up and Running With OCaml
https://www.ocaml.org/docs/up-and-running - Better OCaml (Online prostředí)
https://betterocaml.ml/?version=4.14.0 - OCaml file extensions
https://blog.waleedkhan.name/ocaml-file-extensions/ - First thoughts on Rust vs OCaml
https://blog.darklang.com/first-thoughts-on-rust-vs-ocaml/ - Standard ML of New Jersey
https://www.smlnj.org/ - Programming Languages: Standard ML – 1 (a navazující videa)
https://www.youtube.com/watch?v=2sqjUWGGzTo - 6 Excellent Free Books to Learn Standard ML
https://www.linuxlinks.com/excellent-free-books-learn-standard-ml/ - SOSML: The Online Interpreter for Standard ML
https://sosml.org/ - ML (Computer program language)
https://www.barnesandnoble.com/b/books/other-programming-languages/ml-computer-program-language/_/N-29Z8q8Zvy7 - Strong Typing
https://perl.plover.com/yak/typing/notes.html - What to know before debating type systems
http://blogs.perl.org/users/ovid/2010/08/what-to-know-before-debating-type-systems.html - Types, and Why You Should Care (Youtube)
https://www.youtube.com/watch?v=0arFPIQatCU - DynamicTyping (Martin Fowler)
https://www.martinfowler.com/bliki/DynamicTyping.html - DomainSpecificLanguage (Martin Fowler)
https://www.martinfowler.com/bliki/DomainSpecificLanguage.html - Language Workbenches: The Killer-App for Domain Specific Languages?
https://www.martinfowler.com/articles/languageWorkbench.html - Effective ML (Youtube)
https://www.youtube.com/watch?v=-J8YyfrSwTk - Why OCaml (Youtube)
https://www.youtube.com/watch?v=v1CmGbOGb2I - Try OCaml
https://try.ocaml.pro/ - CSE 341: Functions and patterns
https://courses.cs.washington.edu/courses/cse341/04wi/lectures/03-ml-functions.html - Comparing Objective Caml and Standard ML
http://adam.chlipala.net/mlcomp/ - What are the key differences between Standard ML and OCaml?
https://www.quora.com/What-are-the-key-differences-between-Standard-ML-and-OCaml?share=1 - Cheat Sheets (pro OCaml)
https://www.ocaml.org/docs/cheat_sheets.html - Think OCaml: How to Think Like a (Functional) Programmer
https://www.greenteapress.com/thinkocaml/thinkocaml.pdf - The OCaml Language Cheat Sheet
https://ocamlpro.github.io/ocaml-cheat-sheets/ocaml-lang.pdf - Syllabus (FAS CS51)
https://cs51.io/college/syllabus/ - Abstraction and Design In Computation
http://book.cs51.io/ - Learn X in Y minutes Where X=Standard ML
https://learnxinyminutes.com/docs/standard-ml/ - CSE307 Online – Summer 2018: Principles of Programing Languages course
https://www3.cs.stonybrook.edu/~pfodor/courses/summer/cse307.html - CSE307 Principles of Programming Languages course: SML part 1
https://www.youtube.com/watch?v=p1n0_PsM6hw - CSE 307 – Principles of Programming Languages – SML
https://www3.cs.stonybrook.edu/~pfodor/courses/summer/CSE307/L01_SML.pdf - History of programming languages
https://devskiller.com/history-of-programming-languages/ - History of programming languages (Wikipedia)
https://en.wikipedia.org/wiki/History_of_programming_languages - The Evolution Of Programming Languages
https://www.i-programmer.info/news/98-languages/8809-the-evolution-of-programming-languages.html - Evoluce programovacích jazyků
https://ccrma.stanford.edu/courses/250a-fall-2005/docs/ComputerLanguagesChart.png - Currying
https://sw-samuraj.cz/2011/02/currying/ - Currying (Wikipedia)
https://en.wikipedia.org/wiki/Currying - Currying (Haskell wiki)
https://wiki.haskell.org/Currying - Haskell Curry
https://en.wikipedia.org/wiki/Haskell_Curry - Moses Schönfinkel
https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel - So You Want to be a Functional Programmer (Part 1)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-1–1f15e387e536 - So You Want to be a Functional Programmer (Part 2)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-2–7005682cec4a - So You Want to be a Functional Programmer (Part 3)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-3–1b0fd14eb1a7 - So You Want to be a Functional Programmer (Part 4)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-4–18fbe3ea9e49 - So You Want to be a Functional Programmer (Part 5)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-5-c70adc9cf56a - So You Want to be a Functional Programmer (Part 6)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-6-db502830403 - Python to OCaml: Retrospective
http://roscidus.com/blog/blog/2014/06/06/python-to-ocaml-retrospective/ - Why does Cambridge teach OCaml as the first programming language?
https://www.youtube.com/watch?v=6APBx0WsgeQ - OCaml and 7 Things You Need To Know About It In 2021 | Functional Programming | Caml
https://www.youtube.com/watch?v=s0itOsgcf9Q - OCaml 2021 – 25 years of OCaml
https://www.youtube.com/watch?v=-u_zKPXj6mw