Jedną z właściwości określających w lub pustym typu jest to, że istnieje funkcja dla każdego typu . W rzeczywistości istnieje unikalna taka funkcja. Jest zatem całkiem uzasadnione, aby ta funkcja była dostarczana jako część standardowej biblioteki. Często nazywa się to czymś w rodzaju . (W systemach z podtypów, może to być traktowane jedynie poprzez jest podtypem każdego typu. Następnie ukryte konwersji . Innym, sposobem jest określenie jako , który może być po prostu instancję do dowolny typ).⊥⊥ → AZA⊥ ⊥ ∀ α . αabsurd
⊥absurd
⊥∀ α . α
Zdecydowanie chcesz mieć taką funkcję lub jej odpowiednik, ponieważ pozwala ona korzystać z funkcji, które tworzą . Na przykład, powiedzmy, że mam podano typ suma . Robię na nim analizę przypadków, aw przypadku rzucę wyjątek za pomocą . W razie użyję . Ogólnie rzecz biorąc, chcę wartość typu , więc muszę coś zrobić, aby zamienić do . To pozwoliłoby mi to zrobić.⊥mi+ Amithrow:E→⊥Af:A→BB⊥Babsurd
Powiedział, że nie ma zbyt dużo rozumu, aby zdefiniować własne funkcje . Z definicji byłyby to koniecznie przypadki . Mimo to możesz to zrobić, jeśli nie jest to zapewnione przez bibliotekę standardową, lub potrzebujesz specjalizowanej wersji, która ułatwi sprawdzanie / wnioskowanie typu. Można jednak łatwo produkować funkcje, które zakończy się instancja do rodzaju jak .⊥→A⊥ → Aabsurd
absurd
⊥→A
Chociaż nie ma wiele powodów, by pisać taką funkcję, powinna ona być ogólnie dozwolona . Jednym z powodów jest to, że upraszcza narzędzia / makra do generowania kodu.
Derek Elkins opuścił SE
źródło
(x ? 3 : throw new Exception())
zostaje zastąpione do celów analizy czymś bardziej podobnym(x ? 3 : absurd(throw new Exception()))
?absurd
absurd
throw
Aby dodać do tego, co powiedziano o funkcji
absurd: ⊥ -> a
, mam konkretny przykład tego, gdzie ta funkcja jest rzeczywiście przydatna.Rozważ typ danych Haskell,
Free f a
który reprezentuje ogólną strukturę drzewa zf
węzłami w kształcie i liśćmi zawierającymia
s:data Free f a = Op (f (Free f a)) | Var a
Drzewa te można złożyć za pomocą następującej funkcji:
W skrócie, operacja ta umieszcza się
alg
w węzłach igen
liściach.Teraz do rzeczy: wszystkie rekurencyjne struktury danych mogą być reprezentowane przy użyciu typu danych o stałym punkcie. W Haskell jest to
Fix f
i może być zdefiniowane jakotype Fix f = Free f ⊥
(tj. Drzewa zf
węzłami w kształcie i bez liści poza funktoremf
). Tradycyjnie ta struktura ma również fałd, zwanycata
:Co daje całkiem zgrabne użycie absurdu: ponieważ drzewo nie może mieć żadnych liści (ponieważ ⊥ nie ma innych mieszkańców niż
undefined
), nigdy nie jest możliwe użycie tegogen
do złożenia i toabsurd
ilustruje!źródło
Typ dolny jest podtypem każdego innego typu, co może być niezwykle przydatne w praktyce. Na przykład typ
NULL
w teoretycznej bezpiecznej wersji C musi być podtypem każdego innego typu wskaźnika, w przeciwnym razie nie można np. PowrócićNULL
tam, gdziechar*
oczekiwano; podobnie typundefined
w teoretycznie bezpiecznym języku JavaScript musi być podtypem każdego innego typu w języku.Jako typ zwracanej funkcji bardzo przydatne są również pewne funkcje, które nigdy nie zwracają. Na przykład w silnie napisanym języku z wyjątkami, jaki typ powinien⊥
exit()
lubthrow()
zwrócić? Nigdy nie zwracają kontroli nad swoim rozmówcą. A ponieważ typ dolny jest podtypem każdego innego typu, jest on całkowicie poprawny dla funkcji zwracającejInt
zamiast tego return - to znaczy, że funkcja zwracająca może również w ogóle nie zwracać. (Być może wywołuje , a może przechodzi w nieskończoną pętlę.) Dobrze jest mieć, ponieważ to, czy funkcja kiedykolwiek powróci, czy nie, jest powszechnie nierozstrzygalne.Int
exit()
Wreszcie, jest to bardzo przydatne do pisania ograniczeń. Załóżmy, że chcesz ograniczyć wszystkie parametry po „obu stronach”, podając typ, który musi być nadtypem parametru, oraz inny typ, który musi być podtypem. Ponieważ dno jest podtypem każdego rodzaju, można wyrazić „którykolwiek z podtypów S” jako . Możesz też wyrazić „dowolny typ” jako .⊥≺T≺S ⊥≺T≺⊤
źródło
NULL
czy typ jednostki jest inny niż, jaki jest typ pusty?void*
, potrzebujesz określonego typu, który mógłby być użyty dla dowolnego typu wskaźnika.<:
Myślę o jednym zastosowaniu i jest to coś, co zostało uznane za ulepszenie języka programowania Swift.
Swift ma
maybe
monadę, ortografięOptional<T>
lubT?
. Istnieje wiele sposobów interakcji z nim.Możesz użyć warunkowego rozpakowywania jak
Możesz użyć
map
,flatMap
aby przekształcić wartości!
typu(T?) -> T
), aby wymusić rozpakowanie zawartości, w przeciwnym razie powodując awarięOperator zerowo-koalescencyjny (
??
typu(T?, T) -> T
), aby przyjąć jego wartość lub w inny sposób użyć wartości domyślnej:Niestety, nie było zwięzłego sposobu na powiedzenie „rozpakuj lub wyrzuć błąd” lub „rozpakuj lub awarii z niestandardowym komunikatem o błędzie”. Coś jak
nie kompiluje się, ponieważ
fatalError
ma typ() -> Never
(()
jestVoid
typem jednostkiNever
Swift , jest typem podstawowym Swift). Wywołanie go powodujeNever
, że nie jest zgodny zT
oczekiwanym jako poprawny argument operacji??
.Aby temu zaradzić, zaproponowano propozycję Swift Evolution
SE-0217
- operatora „Unwrap or Die” . Został ostatecznie odrzucony , ale wzbudził zainteresowanie uczynieniemNever
z niego podtypu wszystkich typów.Jeśli
Never
został stworzony jako podtyp wszystkich typów, to poprzedni przykład będzie kompatybilny:ponieważ strona wywoławcza
??
ma typ(T?, Never) -> T
, który byłby zgodny z(T?, T) -> T
podpisem??
.źródło
Swift ma typ „Nigdy”, który wydaje się być podobny do typu dolnego: Funkcja zadeklarowana do zwrócenia Nigdy nie może nigdy powrócić, funkcja o parametrze typu Nigdy nie może zostać nigdy wywołana.
Jest to przydatne w połączeniu z protokołami, w których może istnieć ograniczenie ze względu na system typów języka, że klasa musi mieć określoną funkcję, ale bez wymogu wywoływania tej funkcji i bez wymagań co do typów argumentów byłoby.
Aby uzyskać szczegółowe informacje, powinieneś rzucić okiem na nowsze posty na liście dyskusyjnej swift-evolution.
źródło