Sé cómo algunas ocurrencias negativas pueden ser definitivamente malas:
data False
data Bad a = C (Bad a -> a)
selfApp :: Bad a -> a
selfApp (x@(C x')) = x' x
yc :: (a -> a) -> a
yc f = selfApp $ C (\x -> f (selfApp x))
false :: False
false = yc id
Sin embargo, no estoy seguro de si:
todos los tipos inductivos con ocurrencias negativas pueden resultar incorrectos
si es así, hay una forma mecánica conocida de hacerlo;
Por ejemplo, he estado luchando tratando de hacer que este tipo salga mal:
type Not a = a -> False
data Bad2 a = C2 (Bad2 (Not a) -> a)
Cualquier puntero a la literatura sobre este tema sería apreciado.
lo.logic
type-theory
soundness
Ptival
fuente
fuente
Respuestas:
La razón de la prohibición de sucesos negativos se puede entender por analogía con el teorema de Knaster-Tarski. Este teorema dice que
En lenguajes de tipo dependiente, también tiene tipos indexados y parametrizados, por lo que su tarea real es más complicada. Bob Atkey (que ha blogueado sobre esto aquí y aquí ) me dice que un buen lugar para buscar la historia es:
Como Andrej señala, fundamentalmente si una ocurrencia negativa está bien o no depende de cuál sea su modelo de teoría de tipos. Básicamente, cuando tienes una definición recursiva, estás buscando un punto fijo, y hay muchos teoremas de punto fijo en matemáticas.
Uno de los cuales, personalmente, he utilizado mucho es el teorema del punto fijo de Banach, que dice que si tiene una función estrictamente contractiva en un espacio métrico, entonces tiene un punto fijo único. Esta idea fue introducida en la semántica por (IIRC) Maurice Nivat, y fue ampliamente estudiada por América y Rutten, y recientemente Birkedal y sus colaboradores la conectaron con una técnica operativa popular llamada "indexación por pasos".
Esto da lugar a teorías de tipos en las que se permiten ocurrencias negativas en tipos recursivos, pero solo cuando las ocurrencias negativas ocurren bajo un constructor de tipo especial de "protección". Esta idea fue presentada por Hiroshi Nakano, y la conexión con el teorema de Banach fue hecha tanto por mí como por Nick Benton, así como por Lars Birkedal y sus coautores.
fuente
A veces puedes resolver ecuaciones recursivas "por suerte".
Conclusión: hay dos soluciones, el tipo vacío (que llamó
False
) y el tipo de unidad()
.Integer
(Integer -> Bool) -> Bool
fuente
Es difícil agregar algo a las explicaciones de Andrej o Neel, pero lo intentaré. Voy a tratar de abordar el punto de vista sintáctico, en lugar de tratar de descubrir la semántica subyacente, porque la explicación es más elemental y puedo dar una respuesta más directa a su pregunta.
La referencia crucial es la siguiente:
Mendler, N. (1991). Tipos inductivos y restricciones de tipo en el cálculo lambda de segundo orden. No he encontrado una referencia en línea, me temo. Sin embargo, las declaraciones y pruebas se pueden encontrar en la tesis doctoral de Nax (¡una lectura muy recomendable!).
y entonces
Por supuesto, no está trabajando con tipos definidos equitativamente sino con constructores , es decir, tiene
en lugar de estricta igualdad. Sin embargo, puedes definir
lo cual es suficiente para que este resultado continúe siendo válido:
En su segundo ejemplo, las cosas son un poco más complicadas, ya que tiene algo en la línea de
con
Se resolvería fácilmente si Haskell permitiera tales definiciones de tipo:
En este caso, podría construir un combinador de bucles exactamente de la misma manera que antes. Sospecho que puedes llevar una construcción similar (pero más compleja) usando
El problema aquí es que para construir un isomorfismo
tienes que lidiar con la varianza mixta.
fuente