Obsah
1. Proměnné, funkce a datové typy v jazyku OCaml
2. Přiřazení hodnot k symbolům
6. Použití referencí – jednoduchý čítač
8. Základní datové typy v jazyku OCaml
13. Typová inference u záznamů
14. Repositář s demonstračními příklady
1. Proměnné, funkce a datové typy v jazyku OCaml
Na druhý článek o programovacím jazyku F# dnes opět navážeme „doplňujícím“ článkem, v němž si vysvětlíme stejné koncepty, nyní ovšem upravené do takové podoby, aby je bylo možné přímo využít v programovacím jazyku OCaml. Vzhledem k tomu, že F# vychází právě z OCamlu, neuvidíme prozatím žádné podstatné rozdíly, ovšem jak bude seriál o F#/Ocamlu pokračovat, postupně uvidíme, že se oba na první pohled prakticky totožné jazyky od sebe odlišují a že (možná poněkud paradoxně) je v některých oblastech OCaml dále, než jazyk F#.
Připomeňme si způsob deklarace neměnitelné (immutable) proměnné, tedy proměnné v původním matematickém slova smyslu:
let x = 42;;
OCaml z tohoto zápisu dokáže odvodit typ proměnné (type inference):
val x : int = 42
Typ proměnné je svázán s její hodnotou i jejím identifikátorem.
Pro tisk hodnoty proměnné (nebo jiného výrazu) na standardní výstup můžeme použít funkci nazvanou Printf.printf, které se předává formátovací řetězec a další hodnota či hodnoty. Oproti jazyku F# je zde tedy navíc specifikace jména modulu Printf:
let x = 42;; Printf.printf "x=%d\n" x;;
V REPLu můžeme (alespoň zdánlivě) měnit hodnotu proměnné pomocí deklarace s klíčovým slovem let. Ve skutečnosti však vytvoříme novou proměnnou, která zastíní proměnnou předchozí:
let x = 42;; Printf.printf "x=%d\n" x;; let x = 6502;; Printf.printf "x=%d\n" x;;
Jednou deklarované proměnné lze pochopitelně použít i v dalších výrazech:
let x = 42;; Printf.printf "x=%d\n" x;; let y = x + 1;; Printf.printf "y=%d\n" y;; let z = y + 1;; Printf.printf "x=%d\n" z;;
Proměnné, ke kterým se přistupuje, však již musí existovat (neexistuje zde tedy „automagické“ vytvoření proměnné při jejím prvním použití):
let w = neznama;;
Tento nedostatek překladač opět velmi rychle odhalí:
Line 1, characters 8-9: Error: Unbound value w
2. Přiřazení hodnot k symbolům
Pro deklaraci proměnné (včetně proměnné typu funkce – funkce jsou totiž v OCamlu plnohodnotnými typy) se používá klíčové slovo let, které zajišťuje vazbu (binding) mezi novým symbolem (jménem) a nějakou hodnotou, která je výsledkem nějakého výrazu (a samozřejmě i zde platí, že konstanta je plnohodnotným výrazem). V tom nejjednodušším případě může definice takové vazby vypadat naprosto stejně, jako je tomu v programovacím jazyku F#:
let x = 42
Hodnotu navázanou na symbol pochopitelně můžeme přečíst a nějak ji zpracovat, například vytisknout na terminál:
Printf.printf "x=%d\n" x;;
A můžeme ji použít při definici jiné proměnné:
let y = x + 1 printf "y=%d" y
Na druhou stranu ovšem není možné hodnotu takové proměnné měnit. A podobně jako v programovacím jazyku F#, i v OCamlu znamená symbol = porovnání a nikoli přiřazení, takže se v následujícím příkladu porovnává hodnota x s hodnotou x+1, což znamená, že výsledkem tohoto porovnání bude vždy false:
let x = 42;; Printf.printf "x=%d\n" x;; x = x + 1;; Printf.printf "x=%d\n" x;;
Tento příklad vypíše dvakrát hodnotu 42 a výsledkem výrazu x = x + 1 bude podle očekávání:
- : bool = false
Chování operátoru = si můžeme ověřit na tomto jednoduchém příkladu (povšimněte si, že ve formátovacím řetězci používáme „%b“ a nikoli „%d“, což je opět jazykem kontrolováno):
let x = 42;; Printf.printf "x=%d\n" x;; Printf.printf "x=x+1: %b\n" (x=x+1);; Printf.printf "x=42: %b\n" (x=42);;
Výsledky tohoto prográmku jsou předvídatelné:
x=x+1: false x=42: true
3. Shadowing
Ve druhém článku o programovacím jazyku F# jsme si taktéž ukázali koncept takzvaného shadowingu, kdy jeden symbol může v nějakém bloku zastínit stejně pojmenovaný symbol z vnějšího bloku. V jazyku F# je umožněn následující zápis, v němž se vrací hodnota vnitřního symbolu a (tedy hodnota 2):
let shadow = let a = 1 let a = 2 a printf "%d" shadow
V programovacím jazyce OCaml není tento stručný zápis umožněn. Namísto toho je nutné použít klíčové slovo in, které naznačuje, že každý příkaz let ve skutečnosti vytváří nový blok:
let shadow = let a = 1 in let a = 2 in a;; Printf.printf "%d" shadow
Překladač jazyka OCaml navíc ohlásí, že jedna z proměnných a není použita:
1 Warning : unused variable a.
V interaktivním webovém prostředí vypadá toto varování následovně (povšimněte si, jak se proměnná označí):
Obrázek 1: Varování, že v bloku je nevyužitá proměnná.
4. Neměnitelné hodnoty
V jazyku OCaml jsou hodnoty navázané na symboly (neboli proměnné) neměnitelné, stejně jako v programovacím jazyku F#. Ostatně jazyk F# tuto vlastnost z OCamlu převzal, takže se nejedná o žádnou novinku. Pokusme se, naprosto stejně jako v článku o jazyku F#, nadeklarovat proměnnou x, vypsat její hodnotu a následně ji změnit:
let x = 42;; Printf.printf "x=%d\n" x;; x := x + 1;; Printf.printf "x=%d\n" x;;
Překladač jazyka OCaml v tomto případě vypíše poněkud kryptické chybové hlášení:
Line 4, characters 0-1: Error: This expression has type int but an expression was expected of type 'a ref
Toto hlášení oznamuje, že modifikovat lze pouze referencované hodnoty – viz navazující kapitolu.
This value is not mutable. Consider using the mutable keyword, e.g. 'let mutable x = expression'.
5. Reference
V programovacím jazyku F# je umožněno vytvářet měnitelné proměnné, a to následujícím zápisem:
let mutable x = 42 printf "x=%d" x x <- x + 1 printf "x=%d" x
Tento zápis a vlastně i celý koncept není v jazyce OCaml umožněn (i když modifikátor mutable zde existuje, ale v jiném kontextu). Namísto měnitelných proměnných se používají referencované hodnoty, zkráceně reference nebo jen refy. Referenci si můžeme představit jako proměnnou obsahující adresu hodnoty uložené v operační paměti. Ovšem na rozdíl od céčka s jeho konceptem ukazatelů jsou ve skutečnosti reference v programovacím jazyku OCaml plně typované.
Proměnná obsahující referenci je deklarována s využitím modifikátoru ref:
let x = ref 42
Modifikace referencované hodnoty se provádí s využitím operátoru :=, tedy takto:
x := 0
A pro získání hodnoty se používá operátor !, což je ukázáno na dalším řádku:
Printf.printf "x=%d\n" !x
V jazyce F# je tomu stejně, ovšem existuje zde navíc možnost použít zápis x.Value.
6. Použití referencí – jednoduchý čítač
Podobně jako v článku o programovacím jazyku F# si nyní vytvořme demonstrační příklad, v němž je reference využita pro realizaci jednoduchého čítače. Bude se tedy jednat o proměnnou, jejíž hodnotu lze zvýšit o jedničku, popř. danou hodnotu přečíst. Víme již, že proměnnou s referencí musíme deklarovat s využitím modifikátoru ref, změnu reference zařizuje operátor := a přečtení referencované hodnoty lze realizovat přes operátor !. Zbytek je již jednoduchý:
let x = ref 42;; Printf.printf "x=%d\n" !x;; x := !x + 1;; Printf.printf "x=%d\n" !x;;
Tento prográmek po svém spuštění vypíše:
x=42 x=43
let x = ref 42;; Printf.printf "x=%d\n" x;; x := x + 1;; Printf.printf "x=%d\n" x;;
budeme na tuto chybu upozorněni překladačem:
Line 2, characters 23-24: Error: This expression has type int ref but an expression was expected of type int
7. Standardní funkce incr
V jazyku OCaml existuje standardní funkce nazvaná incr, která dokáže zvýšit referencovanou celočíselnou hodnotu o jedničku (a současně nevrací žádnou hodnotu, k čemuž se záhy dostaneme). Typ této funkce tedy je:
val incr : int ref -> unit
Použití je snadné:
let x = ref 42;; Printf.printf "x=%d\n" !x;; incr x;; Printf.printf "x=%d\n" !x;;
let x = 42;; Printf.printf "x=%d\n" x;; incr x;; Printf.printf "x=%d\n" x;;
Zde překladač objeví a nahlásí chybu, která je „opačná“, než chyba z předchozí kapitoly:
Line 4, characters 5-6: Error: This expression has type int but an expression was expected of type int ref
8. Základní datové typy v jazyku OCaml
Základní datové typy dostupné v jazyku OCaml se v několika ohledech odlišují od typů, s nimiž jsme se seznámili při popisu programovacího jazyka F#. Na jednu stranu je například menší nabídka celočíselných datových typů (což lze řešit knihovnami), protože OCaml nemusí spolupracovat s dalšími jazyky v rámci nějakého ekosystému (jak je tomu v ekosystému .NET), na stranu druhou se v OCamlu rozlišuje mezi měnitelnými (bytes) a neměnitelnými (string) řetězci:
Typ | Stručný popis | Poznámka |
---|---|---|
bool | pravdivostní hodnoty | true a false |
int | celá čísla se znaménkem | buď 31 bitů nebo 63 bitů (ano, jeden bit je rezervován) |
int32 | celá čísla se znaménkem | uložena v 32 bitech |
int64 | celá čísla se znaménkem | uložena v 64 bitech |
float | hodnoty s plovoucí řádovou čárkou | dvojitá přesnost dle IEEE 754 |
char | ASCII znak | ‚A‘ |
string | neměnitelný řetězec | „www.root.cz“ |
bytes | měnitelný řetězec | |
unit | typ s jedinou hodnotou () | viz navazující kapitolu |
list | seznam | [1;2;3] |
array | pole | [|1;2;3|] |
tuple | n-tice | (1, „foo“, ‚b‘) |
9. Typ unit
I v programovacím jazyce OCaml se setkáme s datovým typem unit, s nímž jsme se seznámili ve druhém článku o programovacím jazyce F#. Připomeňme si jen, že se tato hodnota používá v těch místech, kde je formálně nutné zapsat nějaký typ či hodnotu, ale žádný takový typ ani hodnota nedává smysl. Příkladem je opět funkce incr, která provádí nějakou činnost (zvyšuje hodnotu referencované proměnné), ovšem nic nevrací. A právě návratovým typem takové funkce je (). Tento typ se ovšem odlišuje od void z céčka, k čemuž se ještě vrátíme (ve stručnosti – void není plnohodnotným typem).
Způsob zápisu:
()
Výsledná hodnota a její typ:
- : unit = ()
Podívejme se pro úplnost na rozdíl mezi proměnnou typu unit a funkcí bez parametrů:
let x = ();; let y () = 42;;
Rozdíl je patrný při vyhodnocování:
y ;; - : unit -> int = <fun> y() ;; - : int = 42 x ;; - : unit = ()
10. Polymorfické funkce
Jazyk F# převzal koncept polymorfických funkcí právě z programovacího jazyka OCaml, takže je vhodné se o tomto konceptu ve stručnosti zmínit i dnes. Pro ukázku si nadeklarujeme funkci (pro jednoduchost pojmenovanou ident), která akceptuje nějaký parametr pojmenovaný x a hodnotu tohoto parametru vrátí jako svůj výsledek. U této funkce ovšem nikde nespecifikujeme ani typ parametru ani typ návratové hodnoty:
let ident x = x;;
Typ takové funkce bude označen symbolem 'a:
val ident : 'a -> 'a = <fun>
Značka 'a se původně zapisovala jako α a značí generický typ – funkce tedy akceptuje a vrací libovolnou hodnotu. To ovšem znamená, že můžeme bez problémů psát:
Printf.printf "ident=%d\n" (ident 10);; Printf.printf "ident=%s\n" (ident "foo");;
Navíc to znamená, že funkci ident můžeme volat i s hodnotou typu unit (tedy jakoby „bez parametrů“). Zápis musí vypadat takto:
ident();;
Výsledkem bude:
- : unit = ()
Zkusme si nyní složitější příklad polymorfních funkcí, u nichž překladač nemůže dopředu odvodit jejich typ (a proto dosadí 'a):
let ident x = x;; let first x y = x;; let second x y = y;; let add x y = x + y;; ident (add (first 1 2) (second 1 2));;
Výsledkem bude hodnota 3 s typem int:
- : int = 3
To je ovšem zajímavé, protože typy jednotlivých funkcí jsou:
let ident x = x ;; val ident : 'a -> 'a = <fun> let first x y = x ;; val first : 'a -> 'b -> 'a = <fun> let second x y = y ;; val second : 'a -> 'b -> 'b = <fun> let add x y = x + y ;; val add : int -> int -> int = <fun>
Překladač tedy odvodí typ funkcí až ve chvíli, kdy jsou tyto funkce volány (stále se však jedná o čas překladu, nikoli čas běhu aplikace).
11. n-tice
Ze složených datových typů jsou v programovacím jazyku OCaml samozřejmě podporovány n-tice. Ty mohou obsahovat prvky libovolných typů a stejně jako v jazyku F# i zde je typ n-tice jako celku odvozen od typů jednotlivých prvků.
let x = (1, 2, 3);;
Typ této n-tice se zapisuje následovně:
val x : int * int * int = (1, 2, 3)
(true, true) ;; - : bool * bool = (true, true)
Typ této n-tice je bool * bool, tedy kartézský součin dvou množin s prvky typu bool (true či false), takže lze snadno odvodit, kolik unikátních hodnot tento typ reprezentuje.
Můžeme se pokusit zapsat i složitější n-tici:
let x = (1, "foo", (1,2,"bar"));;
Typ této n-tice je:
val x : int * string * (int * int * string) = (1, "foo", (1, 2, "bar"))
12. Záznamy
Jedním z nejužitečnějších složených, (resp. možná přesněji řečeno strukturovaných) datových typů jsou v programovacím jazyku OCaml záznamy (records), v nichž mohou být uloženy prvky taktéž libovolného typu, ovšem na rozdíl od n-tic jsou tyto prvky pojmenovány. Zde je již nutné definovat nový datový typ s explicitním určením typů jednotlivých položek (další možnosti si popíšeme jindy). Datový typ car může být definován takto:
type car = { color: string; model: string; manufacturer: string; year: int; }
Vytvoření proměnné s hodnotou tohoto typu se zapisuje následujícím stylem:
let toyota :car = {color="silver"; model="Corolla"; manufacturer="Toyota"; year=1986};;
Typ této hodnoty je vypsán ve stylu, kdy se již neopisují typy prvků:
val toyota : car
Přístup (pro čtení) k prvkům datové struktury je triviální, resp. přesněji řečeno se nijak zvlášť neliší od mainstreamových programovacích jazyků:
# toyota.model;; - : string = "Corolla"
13. Typová inference u záznamů
I u proměnných či parametrů typu záznam se uplatňuje typová inference, což konkrétně znamená, že například v následujícím příkladu nemusíme u proměnné toyota explicitně deklarovat její typ:
type car = { color: string; model: string; manufacturer: string; year: int; } let toyota = {color="silver"; model="Corolla"; manufacturer="Toyota"; year=1986};;
Výsledkem bude tato hodnota:
val toyota : car = {color = "silver"; model = "Corolla"; manufacturer = "Toyota"; year = 1986}
Někdy je ovšem nutné překladači pomoci a nespoléhat se na typovou inferenci:
type point = { x:int; y:int; } type vector = { x:int; y:int; } let p1 = {x=10; y=10;};; let p2:point = {x=10; y=10;};;
V tomto případě překladač rozhodne, že hodnota p1 bude typu vector a nikoli point:
- : vector = {x = 10; y = 10}
14. 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:
15. 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 - Introduction | OCaml Programming | Chapter 1 Video 1
https://www.youtube.com/watch?v=MUcka_SvhLw&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU - Functional Programming – What | OCaml Programming | Chapter 1 Video 2
https://www.youtube.com/watch?v=JTEwC3HihFc&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=2 - Functional Programming – Why Part 1 | OCaml Programming | Chapter 1 Video 3
https://www.youtube.com/watch?v=SKr3ItChPSI&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=3 - Functional Programming – Why Part 2 | OCaml Programming | Chapter 1 Video 4
https://www.youtube.com/watch?v=eNLm5Xbgmd0&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=4