Bueno, en realidad solo hay un error fundamental cuando se trata de datos corecursive, ¡y eso es usar descuidadamente la recursión en él!
Corecursion implica que los datos se generan incrementalmente en algún sentido. Su función de distancia gráfica es instructiva aquí, porque parece que debería funcionar, entonces piense dónde debe estar la parte incremental: El punto de partida es una distancia de 0 desde un nodo a sí mismo, de lo contrario uno mayor que la distancia mínima entre sus propios vecinos inmediatos. Por lo tanto, cabría esperar que cada valor de distancia sea incremental, lo que significa que necesitamos que sean ellos mismos convenientemente correlativos.
La recursividad en cuestión, entonces, se está produciendo debido a la combinación de (+)
y minimum
: la hora de encontrar el mínimo, 1
siempre habrá menos de 1 + n
, sin tener que preocuparse acerca de lo que es n
... pero no hay manera para comparar Int
s sin calcular todo el valor.
En resumen, el algoritmo espera poder comparar cuántas veces (1 +)
se aplicó a 0
solo hasta donde sea necesario; es decir, quiere números naturales perezosos definidos usando "cero" y "sucesor".
He aquí:
data Nat = Z | S Nat
natToInt :: Nat -> Int
natToInt Z = 0
natToInt (S n) = 1 + natToInt n
instance Show Nat where show = show . natToInt
instance Eq Nat where
Z == Z = True
(S n1) == (S n2) = n1 == n2
_ == _ = False
Z /= Z = False
(S n1) /= (S n2) = n1 /= n2
_ /= _ = True
instance Ord Nat where
compare Z Z = EQ
compare Z (S _) = LT
compare (S _) Z = GT
compare (S n1) (S n2) = compare n1 n2
Y luego, en GHCi:
> distance 1 $ fromList [(0,[1]),(1,[0,2]),(2,[1,3]),(3,[2])]
fromList [(0,1),(1,0),(2,1),(3,2)]
La prueba de que el algoritmo funciona [0]; su implementación fue simplemente incorrecta.
Ahora, como una ligera variación, vamos a aplicar el algoritmo para un gráfico diferente:
> distance 1 $ fromList [(0,[1]),(1,[0]),(2,[3]),(3,[2])]
... ¿qué esperamos esto que ver? ¿Cuál es la distancia desde el nodo 1 para los nodos 2 o 3?
Correr en GHCi tiene la consecuencia obvia:
fromList [(0,1),(1,0),(2,^CInterrupted.
Sin embargo, El algoritmo funciona correctamente en este gráfico. ¿Puedes ver el problema? ¿Por qué colgó en GHCi?
En resumen, es necesario distinguir claramente entre dos formas que no se pueden mezclar libremente:
- perezoso, posiblemente infinitos datos, generados corecursively
- datos finitos, consumido de forma recursiva
Ambas formas se pueden transformar de forma independiente de la estructura (por ejemplo, map
funciona en listas finitas e infinitas). Codata se puede consumir de forma incremental, impulsado por un algoritmo corecursive; los datos se pueden generar recursivamente, limitados por un algoritmo recursivo.
Lo que no se puede do es consumir codata recursivamente (por ejemplo, doblar a la izquierda una lista infinita) o generar datos corecursively (raro en Haskell, debido a la pereza).
[0]: En realidad, fallará en algunas entradas (por ejemplo, algunos gráficos desconectados), pero eso es un problema diferente.
Entonces [traté de usar un tipo de entero perezoso] (https://gist.github.com/1014374), pero obtuve el mismo comportamiento que antes (colgado). ¿Alguna idea de lo que hice mal? – rampion
@rampion: De un vistazo, diría que probablemente sea demasiado estricto en algún lugar cuando se examinan los valores enteros perezosos, que es probable que sea inevitable. Esto en realidad tiene el mismo problema conceptual que usar 'Int', simplemente hace que el problema sea menos obvio. La comparación de valores necesita el signo, y el signo necesita todo el cálculo debido a la resta. Todavía puede ser posible, pero tendría que tener mucho cuidado con las operaciones que no fuerzan más de lo que realmente necesitan. –