Vlastní datové typy budeme od teď používat prakticky v každé hodině, je proto dobré si s nimi rychle potykat.
Definice typů
Typy se v Pyretu (a nejen v něm) dělí na kondicionální (podmíněné) a strukturální. Kondicionální typy používám ve chvíli, kdy jeden typ může nabývat jedné z několika různých hodnot. Strukturální použijeme, pokud má popisovaný objekt nějaké vlastnosti, neboli atributy, které o něm chceme uložit.
Název typu podle konvence začíná velkým písmenem a používá CamelCase
. Dobře se pak odlišuje od jmen funkcí a proměnných.
Strukturální typy
Například: Pokud stavíme databázi zaměstnanců naší firmy, abychom je mohli kontaktovat, když pracují z domu, potřebujeme u každého zaměstnance znát jeho jméno, telefon domů a také jeho přímého nadřízeného (abychom mohli odlišit stejnojmenné zaměstnance z různých týmů).
data Employee: # <--- název typu # vvvv field (pole) employee(name :: String, tel :: String, supervisor-name :: String) # ^^^^^^ data constructor end
Celá databáze by pak mohla být prostě List<Employee>
.
Data constructor je v podstatě obyčejná funkce, která po dodání potřebných parametrů vytvoří objekt typu, který jsme zrovna definovali. Například v případě typu Employee
má datový konstruktor employee
následující typ:
employee :: (String, String, String) -> Employee
Poznámky:
- Protože je data constructor prostě jen funkce, její pojmenovávání se řídí pravidly pro pojmenování funkce (tj. malá písmena a
kebab-case
)
Kondicionální typy
Typickým příkladem je typ Boolean
, který by mohl být definován následujícím způsobem:
data Boolean: # <--- název typu | true # <--- případy, neboli větve | false # <--- zároveň také data constructor end
Další příklad: Na semaforu je zelená NEBO červená NEBO žlutá.
data TrafficLightColor: | green | yellow | red end
a poté můžeme psát
tlc = green # <--- proměnná tlc bude mít typ TrafficLightColor
I zde se na jednotlivé data constructory můžeme dívat jako na funkce, které vrací TrafficLightColor
, avšak na rozdíl od strukturálních typů tyto funkce-konstruktory neberou žádné argumenty. Platí tedy
# vv žádné argumenty green :: (() -> TrafficLightColor) # ^^^^^^^^^^^^^^^^^ vracíme TrafficLightColor yellow :: (() -> TrafficLightColor) red :: (() -> TrafficLightColor)
V jednotlivých větvích můžeme také používat strukturální typy. Zde napříkald zvíře je buďto pes (se jménem a rokem narození) NEBO kočka (se jménem a rokem narození).
data Animal: | dog(name :: String, year :: Number) | cat(name :: String, year :: Number) end
Poznámky:
- Na pořadí případů nezáleží
- Název typu podle konvence začíná velkým písmenem a používá
CamelCase
- Název případu se řídí konvencemi pro pojmenovávání funkcí (tj vše malým a
kebab-case
)
Práce s definovanými typy
Jednolivé případy kondicionálních typů můžeme rozlišit pomocí cases
(case = případ).
cases (<JMENO TYPU>) <PROMENNA DANEHO TYPU>: | <PRIPAD 1> => <VYSLEDEK PRO P1> | <PRIPAD 2> => <VYSLEDEK PRO P2> ... | <PRIPAD N> => <VYSLEDEK PRO PN> end
Například:
fun tl-color-to-string(tlc :: TrafficLightColor): cases (TrafficLightColor) tlc: | Red => "red" | Green => "green" | Yellow => "yellow" end where: tl-color-to-string(Red) is "red" end
Když jsou dané větve zároveň strukturální, můžeme je rovnou "rozbalit" a vytvořit proměnné odpovídající jejich jednotlivým fieldům.
animal = cat("Micka", 2015) result = cases (Animal) animal: | dog(name, _) => "There you go, " + name | cat(_, year) => # <--- jméno kočky nás nezajímá if 2020 - year < 18: "We don't serve cats under 18" else: "Actually, we don't serve any cats" end end check: # <--- jak psát testy mimo where ve funkci result is "We don't serve cats under 20" end
Všimněte si podrtžítek _
značících, že v daném případě nás daný field nezajímá. Na jméno animal
se můžeme také podívat pomocí animal.name
(tzv field accessor), ale je to potenciálně nebezpečné — například ve chvíli, když ne každý případ v Animal
bude mít field name
.