¿Hay algún caso de uso para el tipo inferior como un tipo de parámetro de función?

12

Si una función tiene un tipo de retorno de ⊥ ( tipo inferior ), eso significa que nunca regresa. Puede, por ejemplo, salir o lanzar, ambas situaciones bastante comunes.

Presumiblemente, si una función tuviera un parámetro de tipo ⊥, nunca podría llamarse (con seguridad). ¿Hay alguna razón para definir tal función?

bdsl
fuente

Respuestas:

17

Una de las propiedades que definen el o escribe el vacío es que existe una función para cada tipo . De hecho, existe una función única de este tipo. Por lo tanto, es bastante razonable que esta función se proporcione como parte de la biblioteca estándar. A menudo se llama algo así . (En los sistemas con subtipo, esto podría manejarse simplemente haciendo que sea ​​un subtipo de cada tipo. Entonces la conversión implícita es . Otro enfoque relacionado es definir como que simplemente puede ser instanciado para cualquier tipo.)AAα . αabsurdabsurd α.α

Definitivamente quiere tener una función o un equivalente porque es lo que le permite hacer uso de funciones que producen . Por ejemplo, digamos que me dan una suma Tipo . Hago un análisis de caso y en el caso voy a lanzar una excepción usando . En el caso, voy a usar . En general, yo quiero un valor de tipo así que tengo que hacer algo para convertir una en un . Eso es lo que me dejaría hacer.E+AEthrow:EAf:ABBBabsurd

Dicho esto, no hay un montón de razones para definir sus propias funciones de . Por definición, serían necesariamente instancias de . Aún así, puede hacerlo si no lo proporciona la biblioteca estándar, o si desea una versión especializada en tipos para ayudar a la verificación / inferencia de tipos. Puede, sin embargo, producir fácilmente las funciones que terminarán una instancia de un tipo como .AAabsurdabsurdA

Aunque no hay muchas razones para escribir una función de este tipo, en general aún debería permitirse . Una razón es que simplifica las herramientas / macros de generación de código.

Derek Elkins dejó SE
fuente
Entonces, ¿esto significa que algo así (x ? 3 : throw new Exception())se reemplaza para fines de análisis con algo más parecido (x ? 3 : absurd(throw new Exception()))?
bdsl
Si no tuviera subtipos o no hubiera definido como , entonces el primero no escribiría check y el último sí. Con el subtipo, sí, se insertará algo así implícitamente. Por supuesto, podría poner el interior cuya definición es efectivamente lo que haría definir como . α . α α . αα.αabsurdabsurdthrowα.α
Derek Elkins salió del SE
6

Para agregar a lo que se ha dicho acerca de la función absurd: ⊥ -> a, tengo un ejemplo concreto de dónde esta función es realmente útil.

Considere el tipo de datos Haskell Free f aque representa una estructura de árbol general con fnodos en forma de hojas y hojas que contienen as:

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

Estos árboles se pueden plegar con la siguiente función:

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)

Brevemente, esta operación se coloca algen los nodos y genen las hojas.

Ahora al grano: todas las estructuras de datos recursivas se pueden representar utilizando un tipo de datos de punto fijo. En Haskell esto es Fix fy se puede definir como type Fix f = Free f ⊥(es decir, árboles con fnodos en forma y sin hojas fuera del functor f). Tradicionalmente, esta estructura también tiene un pliegue, llamado cata:

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

Lo que da un uso bastante ordenado de lo absurdo: dado que el árbol no puede tener hojas (ya que ⊥ no tiene habitantes más que undefined), ¡nunca es posible usar el genpara este pliegue e absurdilustra eso!

J_mie6
fuente
2

El tipo inferior es un subtipo de cualquier otro tipo, que puede ser extremadamente útil en la práctica. Por ejemplo, el tipo de NULLen una versión teórica segura de tipo de C debe ser un subtipo de cualquier otro tipo de puntero; de lo contrario, no podría, por ejemplo, regresar a NULLdonde char*se esperaba; Del mismo modo, el tipo de undefinedJavaScript teórico seguro de tipo debe ser un subtipo de cualquier otro tipo en el lenguaje.

Como un tipo de retorno de función, también es muy útil tener ciertas funciones que nunca regresan. En un lenguaje fuertemente tipado con excepciones, por ejemplo, ¿qué tipo debería exit()o debería throw()regresar? Nunca devuelven el flujo de control a su interlocutor. Y dado que el tipo inferior es un subtipo de cualquier otro tipo, es perfectamente válido para una función que retorna Inten lugar de return es decir, una función que regresa también puede elegir no regresar en absoluto. (Tal vez llama , o tal vez entra en un bucle infinito.) Es bueno tenerlo, porque si alguna vez la función regresa o no es famosa por decidir.Intexit()

Finalmente, es muy útil para escribir restricciones. Suponga que desea restringir todos los parámetros en "ambos lados", proporcionando un tipo que debe ser un supertipo del parámetro y otro tipo que debe ser un subtipo. Desde abajo es un subtipo de todo tipo, se puede expresar "cualquier subtipo de S" como . O bien, puede expresar "cualquier tipo" como .TST

Draconis
fuente
3
NULLes un tipo de unidad, ¿no es distinto de ⊥ que es el tipo vacío?
bdsl
No estoy seguro de qué significa ≺ en la teoría de tipos.
bdsl
1
@bdsl El operador curvo aquí es "es un subtipo de"; No estoy seguro de si es estándar, es solo lo que mi profesor usó.
Draconis
1
@ gnasher729 Verdadero, pero C tampoco es particularmente seguro para escribir. Estoy diciendo que si no pudieras simplemente convertir un número entero void*, necesitarías un tipo específico para él que podría usarse para cualquier tipo de puntero.
Draconis
2
Por cierto, la notación más común que he visto para la relación de subtipo es, <:por ejemplo, SistemaF<: .
Derek Elkins dejó SE
2

Se me ocurre un uso, y es algo que se ha considerado como una mejora del lenguaje de programación Swift.

Swift tiene una maybemónada, deletreada Optional<T>o T?. Hay muchas formas de interactuar con él.

  • Puede usar desenvoltura condicional como

    if let nonOptional = someOptional {
        print(nonOptional)
    }
    else {
        print("someOptional was nil")
    }
    
  • Puedes usar map, flatMappara transformar los valores

  • El operador de desenvolvimiento forzado ( !de tipo (T?) -> T) para desenvolver forzosamente el contenido, de lo contrario desencadena un bloqueo
  • El operador de fusión nula ( ??de tipo (T?, T) -> T) para tomar su valor o usar un valor predeterminado:

    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
    

Desafortunadamente, no había una forma concisa de decir "desenvolver o lanzar un error" o "desenvolver o bloquearse con un mensaje de error personalizado". Algo como

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

no se compila, porque fatalErrortiene tipo () -> Never( ()es Void, tipo de unidad NeverSwift , es el tipo inferior de Swift). Llamarlo produce Never, que no es compatible con lo Tesperado como un operando correcto de ??.

En un intento de remediar esto, Swift Evolution propsoal SE-0217: se presentó el operador " Desenvolver o morir" . Finalmente fue rechazado , pero despertó interés en hacer que Neversea ​​un subtipo de todo tipo.

Si Neverfue hecho para ser un subtipo de todos los tipos, entonces el ejemplo anterior será compilable:

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

porque el sitio de llamada de ??tiene tipo (T?, Never) -> T, que sería compatible con la (T?, T) -> Tfirma de ??.

Alexander - Restablece a Monica
fuente
0

Swift tiene un tipo "Nunca" que parece ser bastante parecido al tipo inferior: Una función declarada para devolver Nunca nunca puede regresar, una función con un parámetro de tipo Nunca nunca se puede llamar.

Esto es útil en relación con los protocolos, donde puede haber una restricción debido al sistema de tipos del lenguaje de que una clase debe tener una determinada función, pero sin el requisito de que esta función deba llamarse alguna vez, y sin ningún requisito sobre qué tipos de argumentos sería.

Para obtener más información, debe echar un vistazo a las publicaciones más recientes en la lista de correo de evolución rápida.

gnasher729
fuente
77
"Las publicaciones más recientes en la lista de correo de evolución rápida" no son una referencia muy clara o estable. ¿No hay un archivo web de la lista de correo?
Derek Elkins dejó SE