Czy istnieje jakikolwiek przypadek użycia typu dolnego jako typu parametru funkcji?


12

Jeśli funkcja ma typ zwracany ⊥ ( typ dolny ), oznacza to, że nigdy nie zwraca. Może na przykład wyjść lub rzucić, obie dość zwyczajne sytuacje.

Przypuszczalnie jeśli funkcja miała parametr typu ⊥, nigdy nie mogłaby (bezpiecznie) zostać wywołana. Czy są kiedykolwiek jakieś powody, by zdefiniować taką funkcję?

Odpowiedzi:


17

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).AAα . αabsurdabsurd α.α

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ć.E+AEthrow:EAf:ABBBabsurd

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 .AAabsurdabsurdA

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.


Oznacza to, że coś takiego (x ? 3 : throw new Exception())zostaje zastąpione do celów analizy czymś bardziej podobnym (x ? 3 : absurd(throw new Exception()))?
bdsl

Jeśli nie masz podtytułu lub nie zdefiniowałeś jako , to ten pierwszy nie napisałby check, a drugi zrobiłby to. W przypadku podtytułu tak, domyślnie wstawiono by coś podobnego . Oczywiście, możesz umieścić w środku definicję tego, co faktycznie zrobiłoby zdefiniowanie jako . α . α α . αα.αabsurdabsurdthrowα.α
Derek Elkins opuścił SE

6

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 aktóry reprezentuje ogólną strukturę drzewa z fwęzłami w kształcie i liśćmi zawierającymi as:

data Free f a = Op (f (Free f a)) | Var a

Drzewa te można złożyć za pomocą następującej funkcji:

fold :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
fold gen alg (Var x) = gen x
fold gen alg (Op x) = alg (fmap (fold gen alg) x)

W skrócie, operacja ta umieszcza się algw węzłach i genliś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 fi może być zdefiniowane jako type Fix f = Free f ⊥(tj. Drzewa z fwęzłami w kształcie i bez liści poza funktorem f). Tradycyjnie ta struktura ma również fałd, zwany cata:

cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg x = fold absurd alg x

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 tego gendo złożenia i to absurdilustruje!


2

Typ dolny jest podtypem każdego innego typu, co może być niezwykle przydatne w praktyce. Na przykład typ NULLw teoretycznej bezpiecznej wersji C musi być podtypem każdego innego typu wskaźnika, w przeciwnym razie nie można np. Powrócić NULLtam, gdzie char*oczekiwano; podobnie typ undefinedw 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()lub throw()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ącej Intzamiast 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.Intexit()

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 .TST


3
NULLczy typ jednostki jest inny niż, jaki jest typ pusty?
bdsl

Nie jestem do końca pewien, co ≺ oznacza w teorii typów.
bdsl

1
@bdsl Zakrzywiony operator tutaj jest „jest podtypem”; Nie jestem pewien, czy to standard, to po prostu to, czego używał mój profesor.
Draconis

1
@ gnasher729 To prawda, ale C również nie jest szczególnie bezpieczny dla typu. Mówię, że jeśli nie możesz po prostu rzucić liczby całkowitej void*, potrzebujesz określonego typu, który mógłby być użyty dla dowolnego typu wskaźnika.
Draconis


2

Myślę o jednym zastosowaniu i jest to coś, co zostało uznane za ulepszenie języka programowania Swift.

Swift ma maybemonadę, ortografię Optional<T>lub T?. Istnieje wiele sposobów interakcji z nim.

  • Możesz użyć warunkowego rozpakowywania jak

    if let nonOptional = someOptional {
        print(nonOptional)
    }
    else {
        print("someOptional was nil")
    }
    
  • Możesz użyć map, flatMapaby przekształcić wartości

  • Wymuś operację rozpakowania ( !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:

    let someI = Optional(100)
    print(someI ?? 123) => 100 // "left operand is non-nil, unwrap it.
    
    let noneI: Int? = nil
    print(noneI ?? 123) // => 123 // left operand is nil, take right operand, acts like a "default" value
    

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

let someI: Int? = Optional(123)
let nonOptionalI: Int = someI ?? fatalError("Expected a non-nil value")

nie kompiluje się, ponieważ fatalErrorma typ () -> Never( ()jest Voidtypem jednostki NeverSwift , jest typem podstawowym Swift). Wywołanie go powoduje Never, że nie jest zgodny z Toczekiwanym jako poprawny argument operacji ??.

Aby temu zaradzić, zaproponowano propozycję Swift Evolution SE-0217- operatora „Unwrap or Die” . Został ostatecznie odrzucony , ale wzbudził zainteresowanie uczynieniem Neverz niego podtypu wszystkich typów.

Jeśli Neverzostał stworzony jako podtyp wszystkich typów, to poprzedni przykład będzie kompatybilny:

let someI: Int? = Optional(123)
let nonOptionalI: Int = someI ?? fatalError("Expected a non-nil value")

ponieważ strona wywoławcza ??ma typ (T?, Never) -> T, który byłby zgodny z (T?, T) -> Tpodpisem ??.


0

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.


7
„Nowsze posty na liście dyskusyjnej szybkiego ewolucji” nie są zbyt jasnym ani stabilnym odniesieniem. Czy nie ma archiwum internetowego listy adresowej?
Derek Elkins opuścił SE
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.