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.
Přehled komentářů