Sucesos negativos "guardados" en la definición de tipos inductivos, ¿siempre malos?

11

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.

Ptival
fuente
1
¿Esto es Coq? Haskell? Teoría del pseudo tipo? ¿Qué quieres decir con "ir mal"?
Dave Clarke
@DaveClarke Lo sentimos, el código es Haskell, pero la preocupación es más sobre idiomas como Coq o Agda, donde están prohibidas las ocurrencias negativas. Por "equivocarse", me refiero a poder escribir un término que diverge, y así poder habitar Falso como lo hice en mi ejemplo en Haskell.
Ptival

Respuestas:

10

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

si es una red completa yf : L L es una función monótona en L , entonces el conjunto de puntos fijos de f también es una red completa. En particular, hay un punto mínimo fijo μ f y un mayor punto fijo ν f .Lf:LLLfμfνf

Lpqqp

Ce:PQQQ

N=μα.1+α F:CCe:PQF(e):F(P)F(Q)FF(gf)=F(g)F(f)

μα.F(α)F

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.

Neel Krishnaswami
fuente
7

A veces puedes resolver ecuaciones recursivas "por suerte".

A(A)A.
  1. AA

    AA1.
    1
  2. A()1

Conclusión: hay dos soluciones, el tipo vacío (que llamó False) y el tipo de unidad ().

A(A2)2,
data Cow a = Moo ((a -> Bool) -> Bool)

A22AA22A

N22N.
2N22NInteger(Integer -> Bool) -> Bool
Andrej Bauer
fuente
3

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!).

Bad

Bad=BadA

A

λx:Bad.x x:BadA

y entonces

(λx:Bad.x x) (λx:Bad.x x):A

Bad=F(Bad)
F(X)XF(X)

Por supuesto, no está trabajando con tipos definidos equitativamente sino con constructores , es decir, tiene

data Bad = Pack (Bad -> A)

en lugar de estricta igualdad. Sin embargo, puedes definir

unpack :: Bad -> (Bad -> A)
unpack (Pack f) = f

lo cual es suficiente para que este resultado continúe siendo válido:

 (\x:Bad -> unpack x x) (Pack (\x:Bad -> unpack x x))

A


En su segundo ejemplo, las cosas son un poco más complicadas, ya que tiene algo en la línea de

Bad=BadA

BadBadBad aBad (Not a)

type Not a = a -> False

con

data Not a = Not a

Se resolvería fácilmente si Haskell permitiera tales definiciones de tipo:

type Acc = Not Acc

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

data Acc = D (Not Acc)

El problema aquí es que para construir un isomorfismo

Bad Acc <-> Bad (Not Acc)

tienes que lidiar con la varianza mixta.

cody
fuente