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:A→B . 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()Int⊥Intexit()

⊥≺T≺S⊥≺T≺⊤


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.