Bueno, algo conocido como parametricidad nos dice que si consideramos el subconjunto puro de ML (es decir, sin recursión infinita ref
y todas esas cosas raras), no hay forma de definir una función con este tipo que no sea la que devuelve el vacío lista.
Todo esto comenzó con el artículo de Wadler "¡ Teoremas gratis! ". Este artículo, básicamente, nos dice dos cosas:
- Si consideramos lenguajes de programación que satisfacen ciertas condiciones, podemos deducir algunos teoremas geniales simplemente mirando la firma de tipo de una función polimórfica (esto se llama Teorema de Parametricidad).
- ML (sin recursión infinita
ref
y todas esas cosas raras) satisface esas condiciones.
Desde el teorema Parametricity sabemos que si tenemos una función f : 'a list -> 'b list
, a continuación, para todos 'a
, 'b
, 'c
, 'd
y para todas las funciones g : 'a -> 'c
, h : 'b -> 'd
tenemos:
map h ∘ f = f ∘ map g
(Nota, f
a la izquierda tiene tipo 'a list -> 'b list
y f
a la derecha es 'c list -> 'd list
).
Somos libres de elegir lo g
que queramos, así que deja 'a = 'c
y g = id
. Ahora desde map id = id
(fácil de probar por inducción en la definición de map
), tenemos:
map h ∘ f = f
Ahora deja 'b = 'd = bool
y h = not
. Asumamos que para algunos zs : bool list
sucede eso f zs ≠ [] : bool list
. Está claro que map not ∘ f = f
no se sostiene, porque
(map not ∘ f) zs ≠ f zs
Si el primer elemento de la lista de la derecha es true
, entonces, a la izquierda, el primer elemento es false
y viceversa.
Esto significa que nuestra suposición es incorrecta y f zs = []
. ¿Terminamos? No.
Asumimos que 'b
es bool
. Hemos demostrado que cuando f
se invoca con type f : 'a list -> bool list
para any 'a
, f
siempre debe devolver la lista vacía. ¿Puede ser que cuando llamamos f
ya f : 'a list -> unit list
que devuelve algo diferente? Nuestra intuición nos dice que esto no tiene sentido: simplemente no podemos escribir en ML pura una función que siempre devuelve la lista vacía cuando queremos que nos dé una lista de booleanos y de lo contrario podría devolver una lista no vacía. Pero esto no es una prueba.
Lo que queremos decir es que f
es uniforme : si siempre devuelve la lista vacía para bool list
, entonces tiene que devolver la lista vacía para unit list
y, en general, cualquier 'a list
. Esto es exactamente de lo que trata el segundo punto en la lista de viñetas al comienzo de mi respuesta.
El artículo nos dice que en ML f
debe tomar valores relacionados con valores relacionados . No voy a entrar en detalles sobre las relaciones, es suficiente decir que las listas están relacionadas si y solo si tienen la misma longitud y sus elementos están relacionados por pares (es decir, [x_1, x_2, ..., x_m]
y [y_1, y_2, ..., y_n]
están relacionados si y solo si m = n
y x_1
están relacionados con y_1
y x_2
está relacionado con y_2
y así sucesivamente). Y la parte divertida es, en nuestro caso, dado que f
es polimórfico, ¡ podemos definir cualquier relación en los elementos de las listas!
Vamos a recoger cualquier 'a
, 'b
y miran f : 'a list -> 'b list
. Ahora mira f : 'a list -> bool list
; Ya hemos demostrado que en este caso f
siempre devuelve la lista vacía. Ahora postulamos que todos los elementos de 'a
están relacionados entre sí (recuerde, podemos elegir cualquier relación que queramos), esto implica que cualquiera zs : 'a list
está relacionado con sí mismo. Como sabemos, f
toma valores relacionados con valores relacionados, esto significa que f zs : 'b list
está relacionado con f zs : bool list
, pero la segunda lista tiene una longitud igual a cero, y dado que la primera está relacionada con ella, también está vacía.
Para completar, mencionaré que hay una sección sobre el impacto de la recursividad general (posible no terminación) en el documento original de Wadler, y también hay un documento que explora teoremas libres en presencia de seq
.