Vlastní datové typy

Odpřednášena v úterý 3. 11.

Byl zde zadán #U8, #U9, #U10

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:

  1. 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:

  1. Na pořadí případů nezáleží
  2. Název typu podle konvence začíná velkým písmenem a používá CamelCase
  3. 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.