Esiste un caso d'uso per il tipo inferiore come tipo di parametro di funzione?


12

Se una funzione ha un tipo di ritorno di ⊥ ( tipo di fondo ), significa che non ritorna mai. Può ad esempio uscire o lanciare, entrambe situazioni abbastanza ordinarie.

Presumibilmente se una funzione avesse un parametro di tipo ⊥ non potrebbe mai essere chiamata (in modo sicuro). Ci sono mai dei motivi per definire una tale funzione?

Risposte:


17

Una delle proprietà che definiscono il o tipo vuoto è che esiste una funzione A per ogni tipo A . In effetti, esiste una tale funzione unica . È pertanto ragionevole che questa funzione sia fornita come parte della libreria standard. Spesso si chiama qualcosa del genere absurd. (Nei sistemi con sottotipo, questo potrebbe essere gestito semplicemente avendo un sottotipo di ogni tipo. Quindi la conversione implicita è absurd. Un altro approccio correlato è definire come α.α che può essere semplicemente istanziato su qualsiasi tipo.)

Sicuramente vuoi avere una tale funzione o un equivalente perché è ciò che ti consente di utilizzare le funzioni che producono . Per esempio, diciamo che mi sono dato un tipo di somma E+A . Faccio un'analisi caso su di esso e nella E caso ho intenzione di un'eccezione utilizzando throw:E . In A caso, userò f:AB . Nel complesso, voglio un valore di tipo B quindi ho bisogno di fare qualcosa per trasformare un in un B . Questo è quello absurdche mi lascerebbe fare.

Detto questo, non c'è un sacco di motivi per definire funzioni di A . Per definizione, sarebbero necessariamente esempi di absurd. Tuttavia, potresti farlo se absurdnon è fornito dalla libreria standard o se desideri una versione specializzata del tipo per aiutare il controllo / inferenza del tipo. È possibile, tuttavia, facilmente produrre funzioni che finiranno istanziato a un tipo come A .

Anche se non c'è molto motivo per scrivere una tale funzione, in genere dovrebbe essere comunque consentito . Uno dei motivi è che semplifica gli strumenti / le macro di generazione del codice.


Quindi questo significa che qualcosa come (x ? 3 : throw new Exception())viene sostituito a scopo di analisi con qualcosa di più simile (x ? 3 : absurd(throw new Exception()))?
bdsl,

Se non hai avuto il sottotipo o non hai definito come α . α , quindi il primo non scriverebbe il controllo e il secondo lo farebbe. Con il sottotipo, sì, qualcosa di simile verrebbe inserito implicitamente. Certo, potresti mettere dentro la definizione di quale è effettivamente ciò che definisce come α . α lo farebbe. α.αabsurdabsurdthrowα.α
Derek Elkins lasciò SE

6

Per aggiungere ciò che è stato detto sulla funzione, absurd: ⊥ -> aho un esempio concreto di dove questa funzione è effettivamente utile.

Considera il tipo di dati Haskell Free f ache rappresenta una struttura ad albero generale con fnodi a forma di foglia e foglie contenenti as:

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

Questi alberi possono essere piegati con la seguente funzione:

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)

In breve, questa operazione posiziona algai nodi e genalle foglie.

Ora al punto: tutte le strutture di dati ricorsive possono essere rappresentate usando un tipo di dati a virgola fissa. In Haskell questo è Fix fe può essere definito come type Fix f = Free f ⊥(cioè Alberi con fnodi a forma di e senza foglie al di fuori del funzione f). Tradizionalmente questa struttura ha anche una piega, chiamata cata:

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

Il che dà un uso abbastanza accurato dell'assurdo: poiché l'albero non può avere foglie (poiché ⊥ non ha altri abitanti oltre undefined), non è mai possibile usare il genper questa piega e lo absurddimostra!


2

Il tipo in basso è un sottotipo di ogni altro tipo, che può essere estremamente utile nella pratica. Ad esempio, il tipo di NULLin una versione teoricamente sicura di tipo C deve essere un sottotipo di ogni altro tipo di puntatore, altrimenti non si potrebbe, ad esempio, tornare NULLdove char*era previsto; allo stesso modo, il tipo di undefinedJavaScript teorico sicuro deve essere un sottotipo di ogni altro tipo nella lingua.

exit()throw()IntIntexit()

TST


3
NULLè un tipo di unità no, distinto da ⊥ qual è il tipo vuoto?
bdsl,

Non sono sicuro di cosa significhi ≺ nella teoria dei tipi.
bdsl,

1
@bdsl L'operatore curvo qui è "è un sottotipo di"; Non sono sicuro che sia standard, è proprio quello che ha usato il mio professore.
Draconis,

1
@ gnasher729 Vero, ma anche C non è particolarmente sicuro. Sto dicendo che se non potessi semplicemente trasmettere un numero intero void*, avresti bisogno di un tipo specifico che possa essere utilizzato per qualsiasi tipo di puntatore.
Draconis,


2

Mi viene in mente un uso, ed è qualcosa che è stato considerato come un miglioramento del linguaggio di programmazione Swift.

Swift ha una maybeMonade, scritta Optional<T>o T?. Esistono molti modi per interagire con esso.

  • È possibile utilizzare come da scartare condizionato come

    if let nonOptional = someOptional {
        print(nonOptional)
    }
    else {
        print("someOptional was nil")
    }
    
  • È possibile utilizzare map, flatMapper trasformare i valori

  • L'operatore di riavvolgimento forzato ( !, di tipo (T?) -> T) per scartare forzatamente il contenuto, altrimenti provoca un arresto anomalo
  • L'operatore a coalescenza zero ( ??, di tipo (T?, T) -> T) per assumere il suo valore o utilizzare in altro modo un valore predefinito:

    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
    

Sfortunatamente, non c'era un modo conciso di dire "scartare o lanciare un errore" o "scartare o andare in crash con un messaggio di errore personalizzato". Qualcosa di simile a

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

non viene compilato perché fatalErrorha tipo () -> Never( ()è Void, tipo di unità NeverSwift , è il tipo in basso di Swift). Chiamandolo produce Never, che non è compatibile con l' Tatteso come operando di destra ??.

Nel tentativo di porre rimedio a questo, Swift Evolution propsoal SE-0217- L'operatore " Unwrap or Die" è stato lanciato. Alla fine è stato respinto , ma ha suscitato interesse nel diventare Neverun sottotipo di tutti i tipi.

Se è Neverstato creato per essere un sottotipo di tutti i tipi, l'esempio precedente sarà compilabile:

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

perché il sito di chiamata di ??ha tipo (T?, Never) -> T, che sarebbe compatibile con la (T?, T) -> Tfirma di ??.


0

Swift ha un tipo "Mai" che sembra essere abbastanza simile al tipo in basso: una funzione dichiarata per non poter mai più tornare, una funzione con un parametro di tipo Mai non può mai essere chiamata.

Ciò è utile in relazione ai protocolli, in cui potrebbe esserci una restrizione a causa del sistema di tipi del linguaggio che una classe deve avere una determinata funzione, ma senza il requisito che questa funzione debba mai essere chiamata e nessun requisito che tipo di argomento sarebbe.

Per i dettagli, dai un'occhiata ai post più recenti nella mailing list di Swift-Evolution.


7
"Post più recenti nella mailing list in rapida evoluzione" non è un riferimento molto chiaro o stabile. Non esiste un archivio web della mailing list?
Derek Elkins lasciò
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.