Hlavní navigace

Úvod do jazyka Reason: reference a generalizace

Radek Miček

Na závěr našeho seriálu si řekneme o referencích, o automatické generalizaci a o omezeních automatické generalizace. Článek je zakončen seznamem zdrojů, kde lze vyčíst další informace o Reasonu.

Doba čtení: 3 minuty

Pole a reference

V minulých dílech jsme se naučili, jak v Reasonu používat funkcionální datové struktury. Reason však není omezen pouze na funkcionální styl programování a k dispozici jsou pole a reference, tedy datové struktury, jejichž obsah lze měnit. Pomocí referencí můžeme například implementovat počítadlo:

let counter = ref(0);
counter := counter^ + 1;
Js.log(counter^);

Konstrukce ref(0) vytvoří novou referenci s hodnotou 0. Znak ^ slouží k dereferencování, takže counter^ vrátí aktuální hodnotu počítadla. Novou hodnotu do reference uložíme pomocí  :=.

Díky referencím lze některé problémy vyřešit přehledněji. Jejich stinnou stránkou naopak je, že umožňují ukládat měnitelný stav do globálních proměnných, což snadno zhorší srozumitelnost programu nebo rozbije reactí komponenty.

Typová inference, automatická generalizace a relaxed value restriction

Mnohokrát jsme viděli, že typy v Reasonu není třeba uvádět explicitně, neboť je kompilátor dovede automaticky odvodit. Této vlastnosti se říká typová inference. Pozoruhodné je, že kompilátor dokáže automaticky odvodit i generické typy. Například:

let rev = {
  let rec rev = (reversed, list) =>
    switch list {
    | [] => reversed
    | [x, ...xs] => rev([x, ...reversed], xs)
    };
  (list) => rev([], list)
};

V případě funkce pro otočení seznamu je odvozen typ (list('a)) => list('a). 'a je typová proměnná (to se pozná podle uvozovky na začátku), která zastupuje libovolný typ. Díky tomu můžeme funkci rev volat s různými typy seznamů. Naneštěstí má automatická generalizace v Reasonu omezení, jemuž se říká relaxed value restriction. Například funkce

let mapLength = List.map(x => List.length(x));

která ze seznamu seznamů udělá seznam délek dostane typ (list(list('_a))) => list(int), kde podtržítko v '_a  značí, že typ není možné generalizovat. Generalizace v Reasonu je omezena vesměs na hodnoty. Volání funkce není hodnota (value), funkce však hodnota je, tudíž problém vyřešíme tak, že z volání funkce List.map(x => List.length(x)) uděláme funkci:

let mapLength = (xs) => List.map((x) => List.length(x), xs);

Této transformaci se říká eta expanze. Opačnému směru se říká eta redukce. Například eta redukce (list) => rev([], list) je rev([]). Kód je přehlednější, ale pokud bychom jej použili v naší funkci rev, narazíme na relaxed value restrictrion.

Otázkou je, proč vůbec v jazyce mít value restriction? Důvodem je přítomnost polí a referencí. Například

let x = ref([]);

nemůžeme generalizovat. Kdybychom x dali typ ref(list('a)), mohli bychom do reference uložit seznam libovolného typu a dereferencováním bychom taktéž mohli získat seznam libovolného typu. To však nedává smysl, správně by mělo jít dereferencováním získat pouze seznam stejného typu, jaký byl do reference uložen.

Každý jazyk, jenž má reference, musí mít i určité omezení generalizace, jinak jeho typový systém nebude fungovat korektně:

  • Java, C# a Scala to řeší tak, že negeneralizují proměnné, ale pouze funkce (např. val ve Scale nemůže být generický).
  • Haskell dovoluje používat reference pouze v monádách, tam ovšem ke generalizaci nedochází.

Další zdroje

Základní informace o Reasonu lze získat z oficiální dokumentace a knihy Exploring ReasonML od Axela Rauschmayera.

MIF18 tip v článku témata

Bohužel, pokročilejší témata jsou momentálně zdokumentována pouze pro OCaml, kde je základním zdrojem manuál OCamlu. O typové inferenci se píše v článcích The Essence of ML Type Inference, jehož autory jsou François Pottier and Didier Rémy, a How OCaml type checker works – or what polymorphism and garbage collection have in common od Olega Kiselyova. Relaxed value restriction vymyslel Jacques Garrigue a popsal v článku Relaxing the Value Restriction.

Budoucnost

Sám jsem zvědav, jakým směrem se vývoj pro web a mobilní aplikace bude ubírat. Osobně si myslím, že tzv. dynamicky typované jazyky jako JavaScript už nemají budoucnost, svět bude patřit staticky typovaným jazykům. Otázkou je, zda Reason nabídne dostatek unikátních možností tak, aby mohl konkurovat jednodušším jazykům jako je Dart nebo Kotlin, které mají momentálně mnohem lepší standardní knihovnu a lepší podporu pro zápis asynchronních programů?

Našli jste v článku chybu?