Budoucnost vs budoucnost , jaký je rozdíl?

Jednou z velkých aktualizací, které jsme provedli v Dart 2 (statická kontrola, runtime zabezpečení, volitelné nové / const, vylepšení knihovny a další), je formalizovat prostor způsobem, který je užitečný i omezující. je náchylný k chybám. To platí zejména v asynchronním programování, kde můžete pro budoucí asynchronní funkce Můžete jim napsat a nebudou reagovat, až bude práce dokončena. Možná před vámi Možná jste použili tento FAQ, což je jedna z nejčastěji kladených otázek, které máme: Budoucnost a budoucnost Jaký je rozdíl? Kterou metodu mám použít a kdy?

Z mého důvodu chci začít TLDR.

TL; DR: Chcete využít 99,99% svého volného času.

Mezitím vám doporučím zapnout ve svých projektech dvě pravidla pro nepouští vlákna:

  • prefer_void_to_null: Pomáhá vám zbavit se starého starožitného zvyku Null.
  • void_checks: Dává vám prázdnou sémantiku kódu, přestože pro zabezpečený kód nejsou požadována.

Zbytek tohoto článku není lehkým čtením. Je to kombinace historie, okrajových stavů a ​​teorie druhů. To, co se snažím psát, je jako vysvětlit Monad, ale snažím se co nejlépe a doufám, že jdete na výlet.

Rychlá budoucnost maličkosti

Chci vám položit triviální otázku, abyste předvedli hodnotu, kterou jste si v tomto příspěvku nemuseli přečíst.

Řekněme, že jsme omezeni na dvě budoucnosti - budoucnost a budoucnost . Co dělají následující řádky kódu?

// nefunguje to ve vašem editoru? Budoucnost f1 = budoucnost . Hodnota (null);
// nefunguje to? Budoucnost f2 = Budoucnost . Hodnota (0);

Pomůže to, když budeme čekat?

Uživatel čeká na budoucnost .value (null); Null n = čekat na budoucnost .val (0);

* pozastavení dramatického účinku *

Odpověď je, že žádný z těchto čtyř řádků nebude zobrazovat chyby v editoru, dokud nevypnete downgrade. Správně, Future vezměte si to a není, protože to je budoucnost Jako takový je 100% legální. Budoucnost Ne s tím!

Druhý a čtvrtý řádek ve výchozím nastavení nebude fungovat vůbec (není to tak „bezpečné“, aby se tímto způsobem hodil budoucí výsledek) a zbytek bude tiše úspěšný.

Takové chování by vás mělo obtěžovat. Ale nebojte se! Řešení už znáte: Budoucnost Použití. Rozebírám, proč tato nevhodná chování fungují tímto způsobem.

Teď, jak mezera funguje

Hodně jsem se hádal o tom, co by bylo pro tento článek nejlepší objednávkou. Zde je tolik pojmů, které lze vysvětlit. Znovu se snažím najít nejužitečnější informace pro méně užitečné informace. Proto, protože prostor je užitečnější než Null v Dart 2, je vhodné začít s neplatnou sémantikou Dart 2.

Hlavní myšlenka za mezerou šipek je, že se nepoužívá prázdná hodnota.

mezera f () {} tisk (f ()); // chyba!

Doufám, že to je to, co očekáváte! Zatím dobrý.

Je třeba poznamenat, že dosažení tohoto cíle a otevření prostoru jako normálního pohledu může být trochu divné:

f (bez argumentu) {print (argument); // Chyba! // získejte argument, ale nemůžeme ho použít! }

A tady, v epizodě 2, naše nová „generalizovaná mezera“ ochlazuje a je silná ... i když matoucí.

Ptám se vás na otázku ... jaký typ hodnoty můžeme předat jako argument f?

f (neplatný argument) {…} f (x); // co je x?

Odpověď zní ... nic! Protože nepovolujeme použití x ve funkci F, můžeme bezpečně říci, že x nemá žádnou hodnotu pro žádnou chybu za běhu.

f (1); // žádný rozdíl ve srovnání s f ("foo"); // žádný rozdíl ve srovnání s f ([1, 2, 3]); Ve srovnání s // // ... nebude žádný rozdíl.

Pokud založíme nejlepší sémantiku na myšlence, že se jedná o nepoužitou hodnotu, dostaneme ji. To znamená, že je to hodnota, kterou lze naplnit čímkoli. Je to jako vakuum: odchozí záznam.

Nyní existují případy, kdy je něco za běhu vždy špatné, ale stále by to mělo způsobit statické chyby. To je skvělá podmínka pro vlákna! A to je opravdu myšlenka na void_checks. Přinutí vás hledat místo, kde můžete projít cokoli a udělat z něj prostor, a já bych povzbudil komunity, aby tuto příležitost měly. Není to vyžadováno pro zdraví, ale přenos něčeho jiného než prostoru do prostoru pro vás pravděpodobně může být náhodný.

Protože všechny jsou zakotveny v teorii nadace, propast hraje dobře s každou částí Dart, dokonce i s budoucností inferenciální typy používané v kontextu:

Budoucnost () .tan ((x) {print (x); // error! x má typ „void“, takže jej nelze vytisknout! // To je to, co chceme!});
// a ano, to je také chyba: Foo f = wait voidFuture ();

V tomto okamžiku může průměrný programátor vědět, co všechno potřebuje vědět o prostoru, aby jej mohl efektivně využívat.

Pokud stále jedete, existuje několik zábavných nuggetů.

I když je povoleno void_checks, není zaručeno, že mezery budou nulové. Zrušit toto:

Třída A {objekt v (objekt O) {}}
Třída B rozšiřuje A {@ trip Object Object f (objekt o) => o; }

Nechceme, aby bylo zrušení nezákonné, protože je bezpečné, prospěšné a může to být drastická změna v šipce 1. Musíme tedy uznat, že mezery mohou mít jakoukoli hodnotu. Také nemůžeme vrátit hodnotu Af (), protože to může být B za běhu!

Místo toho máme inteligentní možnost přepsat objekt jako sesterský typ. Nakonec může mít jakoukoli hodnotu a všechny hodnoty jsou Objects. To není to, co jsme vynalezli, je to pravda. Díky uznání reality je to, co používáme.

Zrušením objektu Bratrů neopustíme požadavek, aby byla prázdná hodnota použita úplně. Snažíme se vzdát, ale záměrně uvolníme tato omezení na několika místech kvůli zpětné kompatibilitě:

dynamický f () => voidFn (); // toto je statický voidFn () jako dynamický; // je to legální

Jedná se o zvláštní příležitosti, které by mohly být legální pro objekty, a my jsme to zrušili, aby Dart 2 byl plynulejší.

Zrušení příbuznosti objektů také znamená, že místo se používá jako parametr typu („specializace šablony“, nemáte lidi C ++). Toto snížení je skvělé pro udržování malých webových aplikací a flutterů tím, že umožňuje:

[1, 2, 3] .String (); // je to legální a tisk [1, 2, 3]

Nakonec se může zdát zbytečné přidat jakýkoli parametr jako neplatný, protože to je forma objektu. Prázdné hodnoty však lze předat do neplatných mezer:

f (přerušeno x) {…} f (voidFn ()); // je to legální

To je užitečné pro řadu, jako je zesměšňování metod, které Mockito zrušil. (Chcete-li se dozvědět více, přiložte F kód k výše uvedenému kódu.)

Shrnutí; na konci:

  • Vesmír je bratrem objektu.
  • Téměř vždy můžete používat věci zdarma.
  • V praxi lze cokoli zrušit.
  • Prostor může být "uložen" na určený cíl a void_checks toto chování omezuje.
  • Prázdnou hodnotu lze přenést do jiných prostor.

Podtyp

Než začneme mluvit o null, je důležité, abychom mluvili o „sub“ typu.

Toto je přirozeně se vyskytující typ v teorii typu, který má krátký akademický popis s několika praktickými aplikacemi.

Pokud přestanete číst tuto část velmi podivně, je to solidní důkaz mého původního TLDR. Pokud si nepřejete, aby byl váš kód tak divný, pravděpodobně budete chtít zrušit. Pojďme se podívat na toto podivné králičí hnízdo a uvidíme, jak dlouho to trvá, hm?

Podtyp je poddruh všech typů. Vložte jednoduchá objektově orientovaná slova, což znamená Osoba. A auto. A zvíře. A každý jiný typ programu, který je vždy napsán.

Pokud to zní absurdně, protože je. Rád to považuji za „náhradní“ typ. Ale „absurdní“ nebo „fiktivní“ typ by pro to mohl být o něco vhodnější název. Místo toho se nazývá podtyp, protože se jedná o podmnožinu typu hierarchie v informatice. ¯ \ _ (tsu) _ / ¯. Formálně můžete zmínit falešnou značku, kterou můžete také rozeznat jako „falešné“ znaménko.

Pokud si chcete představit hodnotu jednotlivce, auta nebo zvířete, nemůžete na nic myslet. A překvapivě, praktické použití pramení z ⊥!

Co když napíšu funkci, která se nikdy nevrací? Existují dva jednoduché způsoby, jak toho dosáhnout:

loopForever () {while (true); // první metoda}
alwaysThrow () {throw výjimka (); // druhá metoda}

Jaký je nejlepší návratový typ pro tyto dvě funkce?

Závisí na. Protože funkce se nikdy nevrací, je návratový typ méně důležitý. Můžete použít jakýkoli typ - dokonce i absurdní kořen, který lze použít v různých jazycích různými způsoby.

V C ++ je k dispozici jako norenturn. V Scale je Rust! Můj oblíbený příklad je Haskell, který často zastavuje program a má nedefinovatelnou funkci, která se běžně používá. Vrátí se, uhádli jste, subtyp.

Pokud přijmeme nedefinovanou funkci, jako je Haskell, v Dart a vyloučíme výjimku, když se volá a vrací návraty, bude to:

Je Fo foo = povinné? val: nespecifikováno ();

V příkladu použití ventil pracuje a spolehlivě se skladuje, když program pracuje správně. Pokud je podmínka neplatná, program přepíše výjimku, pokud není definována (). Je bezpečné "uložit" výsledek undefined () bez ohledu na typ foo, protože takový obchod se nikdy nestane!

není uvedeno () zde není návrat. Ale poučení je, že nemůžeme uvolnit kouzelnici ... což znamená, že dno je stejně prázdné jako sliby. Prázdný prostor. To se nikdy nestane.

Jedna věc, kterou bych měl poznamenat, je, že v závislosti na vašem použití můžete často vrátit prostor, který je bez těchto funkcí. Kód, například loopForever (), může být obvykle spíše chybou než užitečným vzorem. Ale volba je na vás.

Podtyp je vhodný pro seznamy jen pro čtení. V Dart jsou výpisy covariantní, tedy seznam povoleno používat jako seznam. Pokud uděláte tento seznam Pokud se pokusíte zavřít obsahovou řádku, runtime kontroly vás chytí a udělají chyby.

To znamená, že pokud vytvoříme seznam can nemůžeme přidat nic, ale můžeme to vidět jako seznam něčeho:

Seznam intList = <⊥> []; pro (int i v intList) {print (i * 2); // platné, protože se to nikdy nestane}

Při zvažování umístění podkategorie „čítač“ jsou ještě zajímavější situace.

Řekněme, že definujeme funkci s parametrem ⊥:

mezera f (⊥ x) {}

To je téměř opakem nespecifikované () instance. Místo funkce, která se nikdy nevrací, deklarujeme funkci, kterou nelze volat! Pro parametr X není zadána žádná hodnota. Nemůžete projít osobně, protože to není auto a nemůžete se dostat do auta, protože to také není člověk. Jediné, co můžete udělat, je napsat tento nesmysl:

f (nespecifikováno ());

Ale jak jsme již řekli, nedefinovaná () se nikdy nevrátí, takže f () se v tomto případě ještě nikdy nezavolá!

Zápis parametru jako mumkin se může zdát k ničemu, ale je esoterický, protože všechny funkce, které lze nazvat, jsou podmnožinami nemožných funkcí. (Přemýšlejte o tom: nemusí to být funkce, kterou lze volat. Pokud funkce není volána, nezpůsobí chyby za běhu.)

Pokud jste stále se mnou, zhluboka se nadechněte a vtřete se do zad.

Zejména pro kterékoli X je bezpečné převést jakoukoli funkci (X) na funkci (⊥), protože je lepší než používat všechny funkce Dart, protože je jedinečný.

Můžete například uložit a dynamicky zavolat libovolnou funkci jednotky na poli, načíst minulé statické chyby a nahradit je runtime chybami, pokud uděláte chyby:

Funkce (⊥) f; f = (int x) => x + 1;
// Vhodnost 123 jako argumentu f se kontroluje za běhu (dynamický) (123);

Toto je chytrý trik, který vám pomůže.

Teď můžeme mluvit o Null.

Null in Dart 2

Šipka má koncept „sub“ typu (subtyp všech typů), ale to není vše. Tato hodnota je k dispozici také ve 2. šipkách! V Dart to nazýváme Null a hodnota je - uhádli jste - nulová.

Dokážu udělat cokoli null (/ null), protože typ nesmyslu v Dart není tak směšný. To je trochu komplikované.

Poznámka: Nulová hodnota není ve skutečnosti vozidlo a není to osoba. V Dart dostáváme žádosti o nenulové druhy. Takže pokud změníme Dart, pak potřebujeme nový typ efektu, jako je Nic, a to je na nejnižší možné úrovni.

Null má nejen všechny nesmysly podtypu, má také únikový poklop. Potřebujete-li se vrátit z funkce, kterou nemůžete opravdu vrátit nebo zavolat funkci, kterou nelze volat, pomůžeme vám! Můžete odtud jít na nulu! Pokud se podíváte na celý obrázek, abych byl upřímný, bylo by pro nás trochu nespravedlivé dělat něco jiného.

Ale to dává mnoho varování pro jednoduché prohlášení:

Null nic () => null;

Null je nejkonkrétnějším typem pro foo (), takže je to logická volba a dobrý výchozí bod. Analyzátor vás také může varovat, pokud voláte neexistující metody:

nic (). x; // chyba! Neexistuje žádný člen x v null

Ale to může vyvolat falešný dojem o bezpečnosti.

nic (). toString (); // žádná chyba: NUL definuje toString () Foo foo = nic (); // žádná chyba: foo je neplatné
// Přijatelné pro všechny typy parametrů ve f (x), // runtime chyby, pokud f (x) není nula, f (nic ());

Pokud by váš cíl měl být synonymem pro vain (), pak je toto chování, které chcete, a Null je správný typ, který je pro vaši funkci nulový.

Budoucnost Existovaly dobré důvody to navrhnout a fungovalo to, když jsme vyvíjeli novou abstraktní sémantiku. Má však podobné otvory:

(počkejte na budoucnostNull ()). toString (); // žádná chyba! Foo foo = čekat na budoucnostNull (); // žádná chyba!

Ve skutečnosti se vrátí funkce f () do budoucnosti Pokud nastavíte funkci f, očekáváte nulové synonymum! To je nebezpečné, protože můžete použít absolutně nulu všeho druhu, jako cokoli od nuly. Myslím, že to většina lidí chce.

Budoucnost místo toho, proč budoucnost Můžete použít? Všechno jde zpět do podtypu ohromujícího mysli. Budoucnost pro budoucnost, kterou lze očekávat, ale nikdy nekončí, nebo vždy končí omylem Možná si myslíte To je přímo srovnatelné s tím, proč funkce vrací Null.

Stejná logika platí i pro Stream : Používá se pro streamování událostí, které neodesílají žádné události. Toto je prázdný seznam jen pro čtení, pro jaké přímé srovnání lze napsat jako seznam.

Zde je několik triků, které vám poskytnou pokyny: StreamController a Zink . Lze je porovnat s funkcemi. Nejspolehlivějším důvodem pro použití těchto druhů je říci, že by se nikdy neměly používat. Další nejlepší zvuk je, že chcete, aby akceptovali pouze synonyma Null, což je nejlepší, pokud není povoleno void_checks.

Představte si, že Dart neměl nulu jako poklop pro všechny hodnoty. Chcete vytvořit typ Zink <⊥>, který dokáže přijímat pouze události z proudu <<>, které nikdy nepřenášejí události? Pokud je odpověď ano, pak jděte a Zink Udělej to.

Všechna tato použití mají slabou záruku, protože samotná hodnota null je únikový poklop, takže můžete použít samotný typ Null. Nedělejte však chybu, že si myslíte, že hodnota není bunkr, ale únikový poklop sám o sobě. Jeden je bezpečnější a trvanlivější a druhý šetří úsilí vykopávání vzhůru ze železobetonu, když bunkr hoří s vámi.

Stručně řečeno, hlavní způsoby, jak napsat něco jako Null:

  • Může být použit všude jako synonymum pro null
  • Nikdy se nemůžete připojit jako doplněk k vašemu programu
  • Jako prázdný kontejner může být maskován jako jakýkoli jiný typ
  • Jako vstup bez přístupu

Konečné srovnání

Pokud dosáhnete tohoto bodu článku, zasloužíte si nějakou kompenzaci - kontaktujte mě a uvidíme se příště, když si koupíte pivo v Portlandu NEBO, když je to zdarma. :)

Hodně jsem mluvil o podtypu, ale ve skutečnosti jsem nepochopil opak, Top. Horní kolo, jak byste očekávali, je pól naproti nižšímu typu. Ačkoli název pro podtyp šipky je Null, nejvyšší typ šipky je dynamický, objektivní a prázdný (tři hlavy „jedné mince“).

Může se zdát divné, že slova jako „nic“ a „prázdná“ v angličtině znamenají dvě podobné věci v teorii typů. Horní i dolní jsou definice konkrétních typů, takže Null a invalid. Může se zdát divné, že dva nepřátelské typy přitahují kandidáty na stejnou práci! Dartovy podivné otázky a okouzlující situace vás mohou rozhněvat, že nyní čtete, co můžete použít.

Myslím si, že naší chybou bylo vždy doporučit Null jako prostor. V té době bylo snadné skrýt užitečné rady, že uživatelé dostali sémantiku Dart 1. Ale omylem jsme vytvořili velmi ezoterický druh, který se velmi často používá.

Nyní jste si tento článek přečetli. Co myslíš? Očekáváte brzy použitelný případ pro Null? Máte pevnou představu o tom, kde je to užitečné?

A co je důležitější, můžete pomoci šířit evangelium zde, abyste vše zjednodušili?