Vítejte při programování v jazyce Roc!
Tento článek má za cíl naučit vytvářet programy v jazyce Roc. Vedle toho se naučíte psát testy, používat REPL a další.
Instalace
Roc zatím nemá číslovaná vydání (release) ani instalátor, ale můžete si přečíst instrukce jak ho nainstalovat ve vašem operačním systému. Pokud se zaseknete, přátelší lidé vám rádi pomohou, pokud otevřete téma s prosbou o pomoc na kanále #beginners platformy Roc Zulip Chat.
REPL
Pokud máme nainstalováno, začneme s tím, že se seznámíme s Read-Eval-Print-Loop (zkráceně REPL).
Dá se také použít online REPL na https://www.roc-lang.org/repl.
REPL spustíte v terminálu příkazem roc repl a měli byste vidět toto:
jirka@jirka-Precision-T3610:~/roc$ roc repl
The rockin' roc repl
────────────────────────
Enter an expression, or :help, or :q to quit.
» :help
Enter an expression to evaluate, or a definition (like x = 1) to use later.
- ctrl-v + ctrl-j makes a newline
- :q quits
- :help shows this text again
»
Hello, World!
Zkuste napsat v REPLu a stiskněte ENTER:
» "Hello, World!"
REPL by mě zobrazit:
"Hello, World!" : Str
»
Blahopřejeme, právě jste napsali první kód v Rocu.
Pojmenování věcí
Zadali jste výraz Hello, World! a REPL ho vytisknul. Dále vytisknul : Str, protože Str je typ výrazu.
O typech pohovoříme později, proto budeme zatím ignorovat : a cokoliv za ní.
Můžete přiřadit výrazům jména. Napište tyto řádky:
» greeting = "Hi"
» audience = "World"
Od teď až do ukončení REPL můžeme přistupovat k výrazům greeting nebo audience pod těmito jmény. Použijeme je dále.
Aritmetika
Zkusíme použít operátor +, zadejte toto:
» 1 + 1
Měli byste vidět tento výstup:
2 : Num *
»
Podle REPLu je jedna plus jedna rovno dvěma. Funguje to.
Roc zachovává obvyklé pořadí operátorů když použijeme několik aritmetických operátorů jako + a -, avšak můžete použít závorky k přesnému určení jak budou operace probíhat.
» 1 + 2 * (3 - 4)
-1 : Num *
»
Volání funkcí
Zkusíme zavolat funkci:
» Str.concat "Hi " "there!"
"Hi there!" : Str
»
Zde voláme funkci Str.concat a předáváme dva argumenty: řetezec "Hi " a řetězec "there!".
Funkce spojí dva řetězce v jeden (tj. dá jeden za druhý) a vrátí vysledný spojený řetězece "Hi there!".
V Rocu nepotřebujeme zadávat závorky a čárky při volání funkce.
Nemusíme psát Str.concat("Hi ", "there!"), raději napíšeme Str.concat "Hi " "there!".
Jak jsme řekli výše u aritmetických výrazů, můžeme použít závorky, abychom řekli, jak vnořené funkce mají pracovat. Například můžeme napsat toto:
» Str.concat "Birds: " (Num.toStr 42)
"Birds: 42" : Str
»
Kód nejprve zavolá funkci Num.toStr s argumentem 42 a konveruje číslo na řetězec "42" a potom se zavolá funkce Str.concat s argumenty "Birds: " a "42".
Závorky jsou důležité, protože určují, v jakém pořadí se funkce mají volat. Zkusíme závorky odstranit a uvidíme, co se stane:
» Str.concat "Birds: " Num.toStr 42
── TOO MANY ARGS ───────────────────────────────────────────────────────────────
The concat function expects 2 arguments, but it got 3 instead:
8│ Str.concat "Birds: " Num.toStr 42
^^^^^^^^^^
Are there any missing commas? Or missing parentheses?
»
Chyba nám říká, že jsme předali funkci Str.concat příliš mnoho argumentů.
Skutečně, předali jsme tři argumenty:
-
Řetězec
"Birds: " -
Funkci
Num.toStr -
a číslo
42.
To není to, co jsme měli v úmyslu. Závorky okolo Num.toStr 42 vyjasňují, že chceme vyhodnotit tento výraz a ne ho předat jako další dva argumenty funkci Str.concat.
Obě funkce Str.concat a Num.toStr mají v sobě tečku.
V Str.concat je Str jméno modulu a concat je funkce uvnitř tohoto modulu.
Podobně Num je jméno modulu a toStr je funkce uvnitř tohoto modulu.
Podrobněji budeme hovořit o modulech později, nyní nám stačí vědět, že modul je pojmenovaná kolekce funkcí. Probereme i to jak využít modul pro více než jenom toto.
Rozšíření řetězců (vkládání do řetězců)
Jiný zápis funkce Str.concat se nazývá rozšíření řetězců a vypadá takto:
» "$(greeting) there, $(audience)!"
"Hi there, World!" : Str
»
Je to jenom syntaktický cukr pro volání Str.concat několikrát, třeba takto:
Str.concat greeting (Str.concat " there, " (Str.concat audience "!"))
Jednořádkový výraz můžeme vložit do závorek uvnitř rozšíření řetězců, třeba takto:
» "Dva plus tři je: $(Num.toStr (2 + 3))"
"Dva plus tři je: 5" : Str
Mimochodem, je více způsobů jak skládat řetězce, podívejte se do dokumentace modulu Str.
Vytvoření programu
Opustíme REPL a vytvoříme naši první aplikaci.
Vytvořte nový soubor se jménem main.roc a vložte do něj:
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br"
}
import pf.Stdout
import pf.Task
main =
Stdout.line! "I'm a Roc application!"
Zkuste ho spustit příkazem roc dev.
jirka@jirka-Precision-T3610:~/roc/test$ roc dev
I'm a Roc application!
jirka@jirka-Precision-T3610:~/roc/test$
Gratulujeme, právě se vám povedlo vytvořit provní program v jazyce Roc.
Přeskočíme, co je napsáno nad funcí main na později a trochu si pohrajeme.
Definice
Nahradíme funci main tímto:
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
}
import pf.Stdout
import pf.Task
ptáci = 3
leguáni = 2
celkem = Num.toStr (ptáci + leguáni)
main =
Stdout.line! "Máme celkem $(celkem) zvířátek."
Spustíme roc dev ješte jednou a uvidíme:
jirka@jirka-Precision-T3610:~/roc/test$ roc dev
Máme celkem 5 zvířátek.
jirka@jirka-Precision-T3610:~/roc/test$
Soubor main.roc nyní obsahuje 4 definice (zkráceně defs) ptáci, leguáni, celkem a main
Poznámka pro Čechy. Je vidět, že v definicích můžeme používat háčky a čárky, ale raději se přidržíme anglických názvů, abychom se nedostali někdy do problémů.
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
}
import pf.Stdout
import pf.Task
birds = 3
iguanas = 2
total = Num.toStr ( birds + iguanas )
main =
Stdout.line! "There are $(total) animals."
jirka@jirka-Precision-T3610:~/roc/test$ roc dev
There are 5 animals.
jirka@jirka-Precision-T3610:~/roc/test$
Soubor main.roc nyní obsahuje 4 definice (zkráceně defs) birds, iguanas, total a main.
Definice pojmenovávají výrazy.
-
První dvě definice se jmény
birdsaiguanaspojmenovávají výrazy3a2. -
Další definice se jménem
totalvýrazNum.toStr (birds + iguanas). -
Poslední definice přiřazuje jméno
mainvýrazu, který vracíTask. Budeme o tom mluvit později.
Jakmile máme definici, můžeme ji použít v jiných výrazech. Například výraz total odkazuje na birds a iguanas a Stdout.line "There are $(total) animals" odkazuje na total.
V definici můžete používat libovolná písmena nebo číslice, ale definice musí začínat malým písmenem.
Poznámka: Definice jsou konstantní, nemohou být použité vícekrát. Obdržíte chybu pokud se o to pokusíte. Dostaneme chybu pokud použijeme tyto dvě definice ve stejném rozsahu.
birds = 3
birds = 2
Definice funkcí
Dosud jsme volali funkce Num.toStr, Str.concat a Strout.line.
Dále se pokusíme definovat vlastní funkci.
birds = 3
iguanas = 2
total = addAndStringify birds iguanas
main =
Stdout.line! "There are $(total) animals."
addAndStringify = \num1, num2 ->
Num.toStr (num1 + num2)
Nová funkce addAndStringify co jsme definovat přijímá dvě čísla, které sečte a zavolá Num.toStr na výsledek součtu a vátí řetězec.
Syntaxe \num1, num2 → definuje argumenty funkce a výraz po → je tělo funkce.
Kdykoliv je funkce zavolána, výraz v těle funkce je vyhodnocen a vrácen.
Výraz if—then—else
Změníme naši funkci tak, aby vracela prázdný řetězec, je-li výsledek součtu roven nule.
addAndStringify = \num1, num2 ->
sum = num1 + num2
if sum == 0 then
""
else
Num.toStr sum
Udělali jsme dvě veci.
-
Zavedli jsme lokální definici pojmenovanou
suma řekli jsme, že je rovnánum1 + num2. Protože jsme definicisumudělali uvnitř funkceaddAndStringify, je lokální uvnitř této funkce a nemůže být použita vně této funkce. -
Přidali jsme podmínku
if - then - else, aby vracela buď""neboNum.toStr sumv závislosti na tom je-lisum == 0.
Každé if musí být doprovázeno jak slovem then tak i slovem else.
Je-li if bez else, tak je to chyba protože if je výraz a každý výraz musí při vyhodnocení dát hodnotu.
Budeme-li mít někde if bez else, tak výraz může být nevyhodnotitelný a to jazyk Roc nedovoluje.
Výrazy else — if
Můžeme kombinovat if a else a dostaneme else if jako třeba tady
addAndStringify = \num1, num2 ->
sum = num1 + num2
if sum == 0 then
""
else if sum < 0 then
"negative"
else
Num.toStr sum
Poznamenejme, že else if není nové klíčové slovo jazyka. Je to jenom if / else kde větev else obsahuje jiné if / else.
Je to lépe vidět, když použijeme jiné odsazení:
addAndStringify = \num1, num2 ->
sum = num1 + num2
if sum == 0 then
""
else
if sum < 0 then
"negative"
else
Num.toStr sum
Komentáře
Komentáře v jazyce Roc vypadají takto:
# The 'name' field is unused by addAndStringify
Kdykoliv napíšeme znak #, znamená to, že co je potom až do konce řádky je komentář a nemá vliv na program.
Jazyk Roc nemá víceřádkové komentáře.
Dokumentační komentáře
Komentáře začínající jsou takzvané dokumentační komentáře které se objeví v generované dokumentaci (roc docs).
Mohou obsahovat bloky kódu, když přidáme 5 mezer za znaky .
## This is a comment for documentation, and includes a code block.
##
## x = 2
## expect x == 2
Dokuemtační komentáře neovlivňují chod programu.
Poznámka: V současné verzi, kteou mám já, roc docs nefunguje dobře.
Záznamy (records)
Naše funkce addAndStringify má dva argumenty.
Můžeme ji předělat, aby měla jeden argument takto:
total = addAndStringify { birds: 5, iguanas: 7 }
addAndStringify = \counts ->
Num.toStr (counts.birds + counts.iguanas)
Nyní funkce přijímá záznam, což je skupina pojmenovaných hodnot. Záznamy nejsou objekty; nemají metody ani dědičnost, uchovávají pouze informaci.
Výraz { birds: 5, iguanas: 7 } definuje záznam se dvěma položkami (položka birds a položka iguanas) a přiřazuje do položky birds číslo 5 a do položky iguanas číslo 7.
Pořadí položek nehraje roli; můžeme napsat položku iguanas jako první a birds druhou a jazyk Roc to bude považovat za zcela stejný záznam.
Když napíšeme counts.birds tak vlastně přistupujeme k položce birds záznamu counts a když napíšeme counts.iguanas tak přistupujeme k položce iguanas.
Když použijeme == na záznam, jsou porovnány všechny položka v obou záznamech pomocí == a záznamy jsou si rovné, pokud všechny položky jsou si rovné.
Pokd jeden záznam má více položek než druhý, nebo jsou-li typy položek různé, tak od kompilátoru Roc obdržíme chybu při sestavování programu.
Poznámka: Jiné programovací jazyky mají koncep "identická rovnost", což je něco jiného než "strukturální rovnost", kterou jsme právě popsali. Jazyk Roc nemá koncept identické rovnosti; toto je jediná cesta jak rovnost funguje.
Přijímání položek navíc
Funkce addAndStringify přijme libovolný záznam, který má aspoň položky birds a iguanas, může však funkci volat se záznamem o dalších položkách.
Například:
total = addAndStringify { birds: 5, iguanas: 7 }
# The `note` field is unused by addAndStringify
totalWithNote = addAndStringify { birds: 4, iguanas: 3, note: "Whee!" }
addAndStringify = \counts ->
Num.toStr (counts.birds + counts.iguanas)
Funguje to proto, že funkce addAndStringify používá pouze count.birds a cound.iguanas.
Pokud bychom uvnitř funkce addAndStringify použili i counts.note, tak obdržíme chybu, protože total volá funkci addAndStringify a předává záznam, který nemá položku note.
Zkratky v záznamech
Roc má pár zkratek, které můžete použít k vyjádření operací svázaných se záznamy srozumitelněji.
Misto toho, abychom psali \record → record.x můžeme psát .x a bude to vyhodnoceno atejným zpusobem:
funkce bere záznam a vrací jeho položku x.
Můžete to udělat s libovolnou položkou.
Například:
# returnFoo is a function that takes a record
# and returns the `foo` field of that record.
returnFoo = .foo
returnFoo { foo: "hi!", bar: "blah" }
# returns "hi!"
Někdy přiřadíme definici položce, která má stejné jméno, například { x: x }.
V těchto příkladech můžeme zkrátit zápis jmen definicí na jednu, například { x }.
Můžeme to udělat tímto způsobem se všemi položkami, kde to chceme; dále jsou příklady různé způsoby, jak můžeme definovat stejný záznam:
-
{ x: x, y: y } -
{ x, y } -
{ x: x, y } -
{ x, y: y }
Restrukturování záznamů
Místo toho, abychom dávali jména položkám v argumentech funkce, můžeme použít restrukturování.
addAndStringify = \{ birds, iguanas } ->
Num.toStr (birds + iguanas)
Zde jsme restrukturovali záznam a místo toho jsme udělali definici birds, která je přiřazena položce birds a definici iguanas přiřazenou položce iguanas.
Můžeme to přizpůsobit, jestli chceme.
addAndStringify = \{ birds, iguanas: lizards } ->
Num.toStr (birds + lizards)
V této verzi jsme udělali definici lizards (ještěrka) a přiřadili jsem ji položce iguanas.
(Pokud chceme, můžeme něco podobné udělat i s položkou birds.)
Konec konců, restrukturalizace může být provedena i s definicemi:
{ x, y } = { x: 5, y: 10 }
Vytváření záznamů z jiných záznamů
Zatím jsme vytvářeli záznamy z nuly, když jsme určovali všechny položky. Můžeme však konstruovat nové záznamy i tak, že použijeme již existující záznam a přidáme další položky, které budou rozdílné.
original = { birds: 5, zebras: 2, iguanas: 7, goats: 1 }
fromScratch = { birds: 4, zebras: 2, iguanas: 3, goats: 1 }
fromOriginal = { original & birds: 4, iguanas: 3 }
Záznamy fromScratch a fromOriginal jsou stejné, ačkoliv jsou definovány různámi způsoby.
-
fromScratchje udělán stejným postupem, jako jsme postupovali až doteď. -
fromOriginalje vytvořen ze záznamuoriginaltak, že položky záznamuoriginalaž po znak&se převezmou jako implicitní.
Poznamenejme, že & nemůže přidat nové položky nebo změnit typ již existující položky.
(Pokud se o to pokusíte, dostanete chybu při překladu.)
Funguje to takto:
## Jednoduchý program, který ukazuje použití rozšíření řetězců
##
## "There are $(total) animals."
##
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
}
import pf.Stdout
import pf.Task
original = { birds: 5, zebras: 2, iguanas: 7, goats: 1 }
fromScratch = { birds: 4, zebras: 2, iguanas: 3, goats: 1 }
fromOriginal = { original & birds: 2, iguanas: 3 }
main =
Stdout.line! "There are: $(Num.toStr original.birds) birds in original."
Stdout.line! "There are: $(Num.toStr fromScratch.birds) birds in fromScratch."
Stdout.line! "There are: $(Num.toStr fromOriginal.birds) birds in fromOriginal."
jirka@jirka-Precision-T3610:~/roc/test$ roc dev
There are: 5 birds in original.
There are: 4 birds in fromScratch.
There are: 2 birds in fromOriginal.
jirka@jirka-Precision-T3610:~/roc/test$
Chytáme chyby pomocí dbg
Odstraňování chyb pomocí print je nejpoužívanější technika v historii programování a Roc má na to klíčové slovo dbg.
Příklad jak používat dbg:
pluralize = \singular, plural, count ->
dbg count
if count == 1 then
singular
else
plural
Kdykoliv je dosažen řádek s dbg, hodnota count spolu se jménem zdrojového souboru a číslem řádku a sloupce je vypsána na stderr
[pluralize.roc 6:8] 5
N-tice (tuples)
Poznámky
Základní primitivní hodnoty
-
skalární primitiva
-
funkce
-
čísla
-
řetezce
-
-
kolekce
-
seznamy
-
záznamy
-
tagy
-
Všechno v jazyce Roc je konstanta (immutable). Identation má vliv. Pipelining.
Typy
-
sound type system
-
complete type inference
-
no try/catch (error unions instead)
Typy začínají velkým písmenem.
Všechny řetězce jsou UTF-8.