Přejít k navigační liště

Zdroják » JavaScript » Dart Typesystem

Dart Typesystem

Články JavaScript

Ľudia zvyknutí na Java, C#, či C++ ohŕňajú nosom nad tým, že Dart je dynamicky typovaný. Ľudia odchovaní na Pythone, Javascripte či Ruby ohŕňajú nosom nad tým, že Dart to s podporou dynamických features príliš nepreháňa. Ľudia obľubujúci Dart nosom neohŕňajú a Dart-ovský typesystem pokladajú za najlepší vynález hneď po krájanom chlebe. O čom táto kontroverzia vlastne je, a ako to celé funguje?

Nálepky:

Dart o sebe na oficiálnej stránke tvrdí, že je dynamicky typovaný. Toto tvrdenie hneď aj dokladá ukážkou, ktorej parafrázu si ukážeme:

  var i = 10;
  i = new Object();
  i = "dart IS dynamically typed language";
  print(i.length); // vypise 34

Ukážka sa skompiluje, spustí a korektne vypíše výsledok, úplne ignorujúc problém (problém?), že premenná i menila typ (číslo, objekt, string), ako sa nám zachcelo. Ak sa „spýtame“ editora, čo si myslí o type premennej i , (podržíme nad ňou myš), dozvieme sa, že je typu dynamic (čítaj: hocičo). Keby sme do kódu pridali riadok print(i.runtimeType); dozvedáme sa, že i je String.

 Trochu poriadku do toho dynamična

Zameňme v ukážke deklaráciu var i = 10; za num i = 10; . Pre úplnosť: num je spoločným predkom tried int a double. Nastanú dve veci – editor nám ukáže warning a pokiaľ ho budeme ignorovať (jasné, že budeme), program havaruje s hláškou:

"type 'Object' is not a subtype of type 'num' of 'i'."

Na prvý pohľad to môže pôsobiť tak, že pridaním informácie o type premennej i sme zapli akýsi iný – typovo orientovaný – Dart. To však nie je pravda, dokonca, je to úplne MYLNÁ predstava. Dart totiž zo svojej podstaty ignoruje informácie o type. Tie slúžia (doslova) len na okrasu (teda, pomáhajú ľuďom a tiež DartEditoru rozumieť kódu) a na samotný beh kódu nemajú vplyv.

Počujem nesúhlasné hvízdanie? Ak Dart ignoruje typy, tak prečo náš program havaruje s hore uvedenou výnimkou? DartEditor defaultne púšťa kód v tzv. checked móde. V praxi si to môžme predstaviť tak, že za každým priradením sa prevedie kontrola, či premenná má správny typ, ak nie, vyhodí sa výnimka. Schválne, skúste checked mód vypnúť (v run/manage launches) a všetko pobeží hladko ako predtým. Checked mód je zamýšľaný ako pomôcka pre vývoj, že to je fakt dobrá pomôcka mi asi uverí každý, kto skúsil v JavaScripte napísať viac ako 4 riadky kódu.

Pred filozofickým zamyslením na záver ešte dve zastaveniahodné záležitosti.

Typ pre funkciu

Majme funkciu, ktorá ako svoj parameter akceptuje inú funkciu (toto sa v Darte a tiež kope iných kultúrnych jazykov dá spraviť). Vezmime napríklad takýto kus kódu:

num feedAll(List<Animal> animals, dynamic feedOne){
  num consumed = 0;
  for(Animal animal in animals){
    consumed += feedOne(animal);
  }
  print("${consumed} food was consumed");
}

//neskor volame feedAll 

feedAll([alfie, balthazar, carly], (animal){
  num foodConsumed = animal.feed();
  print("feeding ${animal.name}. NOM NOM NOM");
  return foodConsumed;
});

Funkcia feedAll akceptuje ako argument feedOne, ktorého typ sme zatiaľ kvôli nedostatku kreativity špecifikovali ako dynamic. V skutočnosti však vieme, že feedOne má byť funkcia, ktorá akceptuje argument typu Animal a vráti num. Toto teraz odkomunikujeme Dartu. Najprv definujeme typ pre feeding function:

typedef num Feeder(Animal a);

takto definovaný typ môžme použiť nasledovne:

 num feedAll(List<Animal> animals, Feeder feedOne){ //blah

a je to. Opäť, Dart nám na náš typ v podstate kašle, celý typový cirkus je o čitateľnosti a podpore, ktorú dostaneme od DartEditora.

Generics

večný problém. Začnime obligátnym úvodom pre tých, čo ešte nepočuli o:

Kovariancia a kontravatriancia typov

Zabudnime na chvíľu na Dart a vezmime UOOL (Univerzálny Objektovo Orientovaný Lenguidž). Oddeďme z triedy Animal triedu Cat a poďme filozofovať. Každá inštancia Cat is Animal, alebo inak: Cat ponúka všetky metódy čo Animal plus dačo navyše. So far so good, v akom vzťahu ale budú List<Cat> a List<Animal>? A aby to neznelo ako púha filozofická otázka: Môžme napr. funkcii feedAll, (očakávajúcej prvý argument typu List<Animal>) podhodiť parameter typu List<Cat>?

Na prvý pohľad áno, môžme, to je ale len zdanie. Čo ak by feedAll vytvorila nový objekt animal typu Animal (ale už nie Cat) a chcela ho pridať do obdržaného listu mačiek? To je akcia zrelá na Exception! Z pohľadu feedAll je ale všetko v poriadku (pridáva animal do List<Animal>). Celé zle. A aby náhodou nevzniklo zdanie, že sa jedná o collection-only problém:

var catFeeder = (Cat cat) => 0;

je pravda, že catFeeder is Feeder, resp., mal by byť catFeeder validným argumentom pre feedAll (definované vyššie)?

Videl som už viaceré riešenia tohto problému, s každým sa plus mínus dá žiť, ale žiadne ma neokúzlilo. Najzaujímavejšie sa k problému postavila Scala, ktorej autori múdro používajú vedecké pojmy ako kovariantný a kontravariantný, premakali typesystem a všetko čo sa nepodarilo vyriešiť, prehlásili za feature (kto si myslí, že Scala je dokonalá, nech mi napr. skúsi vysvetliť, prečo immutable Set nie je vo svojom type kovariantná).

Generics a Dart

Bez checked módu pracujeme ako v hociakom inom dynamickom jazyku, teda, robíme, čo chceme, pokiaľ neprídeme k problému ako objekt X nemá property Y, všetko bude OK. Pokiaľ zapneme checked mód:

typedef num oneFeeder(Animal);

void main() {
  var catFeeder = (Cat cat) => 0;
  var sthFeeder = (Something sth) => 0;

  print(catFeeder is Feeder); //true
  print(sthFeeder is Feeder); //true

  print(new List<Cat>() is List<Animal>); //true
  print(new List<Object>() is List<Animal>); //false
}

teda, pri typoch funkcií ignorancia typov arguementov, v kolekciách defaultná kovariancia, teda, List<Animal> je supertype List<Cat> preto, lebo Animal je supertype Cat.

Ako to bolo s tým krájaným chlebom

Dartovský typesystem má niečo do seba. Mám v Darte už čosi odkódené a (zatiaľ) som  optimistický: systém pomáha v orientácii v produkčnom kóde, nebrzdí pri prototypovaní, navyše, prakticky nikdy nemusíte bojovať s kompilátorom, aby láskavo pochopil, že váš dobrý kód (z pohľadu dynamického interpretera) je naozaj dobrý (z pohľadu statickej kontroly typov). Väčšinu typových problémov, na ktoré pri písaní Dartovského kódu narazíte, bude mať na svedomí DartEditor, ktorý má v typovej inferencii level UnderBeginner – a má čo doháńať. Pre tých, ktorí rozmýšľajú, čím sa bude treba vyhrážať kolegom/zamestnancom, aby písali do kódu typové anotácie: tým istým, čo používate na to, aby písali dokumentáciu, alebo testy – Dartovské typy sú tak trochu práve týmto.

Komentáře

Subscribe
Upozornit na
guest
4 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Honza

Feeder by mel byt subtype vsech feederu pro jakykoli konkretni zvire, cili Feeder is CatFeeder a zaroven Feeder is DogFeeder atd. Vyznam „subtype“ relace se obecne da chapat jako zastupitelnost, tzn. subtype se da pouzit vsude, kde je ocekavan supertype. Konkretne u Feederu, obecnejsi Feeder ktery umi nakrmit libovolny zvire, muze byt pouzit kdekoli se ocekava catFeeder, protoze zvlada nakrmit cokoli vcetne kocek. Tim padem Feeder is catFeeder.

Kovariance a kontravariance nejdou zadny vedecky pojmy, i takovy mainstreamovy jazyky jako C# nebo Java podporujou variance annotations pro typovy parametry. Ono by to bez nich ve staticky typovanym jazyce totiz moc dobre neslo.

Jakozta fanouska Scaly by me zajimalo, co konkretne se v typovym systemu Scaly nepodarilo vyresit a bylo prohlaseno za feature. Ze Scala, stejne jako cokoli jinyho, neni dokonala, je jasny. Ale co se tyce zrovna typovyho systemu, tam mi prijde jako jeden z nejdomyslenejsich jazyku vubec vedle Haskellu apod. Proc je Set invariantni je vysvetleno tady http://stackoverflow.com/questions/676615/why-is-scalas-immutable-set-not-covariant-in-its-type, TLDR aby Set mohla byt pouzivana jako funkce z typu prvku do bool. Mozna to neni uplne intuitivni, ale to jeste neznamena, ze to je spatne.

Radek Miček

Jakozta fanouska Scaly by me zajimalo, co konkretne se v typovym systemu Scaly nepodarilo vyresit a bylo prohlaseno za feature.

Slabá typová inference.

Ale co se tyce zrovna typovyho systemu, tam mi prijde jako jeden z nejdomyslenejsich jazyku vubec vedle Haskellu apod.

Co to znamená nejdomyšlenější? Třeba bezpečný printf tam nenapíšete (počet a typy parametrů závisí na hodnotě prvního argumentu, což Scala neumí popsat).

Honza

Slabá typová inference.

Ano, neni tak silna jak by teoreticky byt mohla (typy parametru funkci, return type rekurzivni funkce), ale nemyslim si, ze by to bylo prohlasovano za feature.

Co to znamená nejdomyšlenější? Třeba bezpečný printf tam nenapíšete (počet a typy parametrů závisí na hodnotě prvního argumentu, což Scala neumí popsat).

Domyslenosti jsem myslel to, ze neobsahuje ruzne nekonzistence, vyjimky nebo vylozene nesmysly. Existuji i jazyky, ktere jsou na tom jeste lepe a podporuji i vami zminovane dependent types, ale ty povazuju spis za akademicke/experimentalni a rozhodne bych si v nich, narozdil od Scaly, netroufnul vyvijet komercni aplikaci.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.