No es la forma ingenua de intentarlo, que se ve así:
route :: Graph -> Label -> Label -> Bool
route g dest from | from == dest = True
route g dest from = any (route g dest) (neighbours g from)
Pero eso no funciona en bucle gráficos. (También estoy asumiendo que tiene vecinos definidos)
Entonces, qué hacer pero pasar la lista de nodos ya vistos.
route2 :: Graph -> Label -> Label -> [Label] -> Bool
route2 g dest from seen
| dest == from = True
| otherwise = any (\x -> route2 g dest x (from:seen)) (neighbours g from)
Pero si se ejecuta en el gráfico aquí:.
Obtendría una traza que parecía algo como esto (con perdón del esquema, he robado descaradamente estas fotos de mi clase cs es fr encontrar-ruta, y fr-l es una versión de la misma que toma una lista. el segundo parámetro es el acumulador) 
Como se puede ver, termina visitando los nodos K y H dos veces. Esto es malo, veamos por qué está haciendo eso.
Dado que no pasa ninguna información de respaldo de las llamadas recursivas en any
, no puede ver lo que hizo en las ramas que fallaron, solo lo que estaba en la ruta al nodo actual.
Ahora, para solucionar eso, hay dos caminos que podemos seguir. Mi clase adoptó un enfoque de continuación de aprobación que es bastante novedoso, así que lo mostraré primero, antes de la versión de mónada estatal.
routeC :: Graph -> Label -> Label -> [Label] -> ([Label] -> Bool) -> Bool
routeC g dest from seen k
| dest == from = True
| from `elem` seen = k (from:seen)
| otherwise = routeCl g dest (neighbours g from) (from:seen) k
routeCl :: Graph -> Label -> [Label] -> [Label] -> ([Label] -> Bool) -> Bool
routeCl g dest [] seen k = k seen
routeCl g dest (x:xs) seen k =
routeC g dest x seen (\newSeen -> routeCl g dest xs newSeen k)
Esto utiliza un par de funciones, en lugar de cualquiera. routeC
solo comprueba si llegamos al destino o si hemos realizado un bucle, de lo contrario solo llama a routeCL con los vecinos del nodo actual.
Si hemos realizado un bucle, en lugar de solo devolver False
, llamamos a la continuación, pero con los nodos que hemos visto actualmente (incluido el actual).
routeCL
toma una lista de nodos, y si la lista está vacía, ejecuta la continuación, de lo contrario, hace algo interesante. Se ejecuta routeC
en el primer nodo, y le pasa una continuación que ejecutará routeCl
en el resto de la lista, con la NUEVA lista de nodos vistos. Por lo tanto, podrá ver en la historia de las ramas fallidas.
(Como cosa adicional, podemos generalizar esto un poco más y transformarlo por completo en el estilo de paso de continuación. He generalizado también cualquiera, en lugar de usar el par de funciones. Esto es opcional, y el tipo firma es más aterrador que el código.)
anyK :: (a -> s -> (s -> r) -> (s -> r) -> r) ->
[a] -> s -> (s -> r) -> (s -> r) -> r
anyK p [] s tK fK = fK s
anyK p (x:xs) s tK fK = p x s tK (\s' -> anyK p xs s' tK fK)
routeK2 :: Graph -> Label -> Label -> ([Label] -> r) -> ([Label] -> r) -> r
routeK2 g dest from' trueK falseK = route from' [] trueK falseK
where route from seen tK fK
| from == dest = tK seen
| from `elem` seen = fK seen
| otherwise = anyK route (neighbours g from) (from:seen) tK fK
lo mismo, pero con más información que se ha pasado.
Ahora, por lo que has estado esperando, la versión del estado mónada.
routeS :: Graph -> Label -> Label -> State [Label] Bool
routeS g dest from | dest == from = return True
routeS g dest from = do
seen <- get
if from `elem` seen then return False else do
put (from:seen)
anyM (routeS g dest) (neighbours g from)
¿Pero no se parece mucho a la última línea con la que empezamos, solo con un poco de plomería adicional? Compare:
any (route g dest) (neighbours g from) -- Simple version
anyM (routeS g dest) (neighbours g from) -- State Version
anyK route (neighbours g from) (from:seen) tK fK -- CPS version
En el núcleo, los tres están haciendo lo mismo. La mónada en la versión de estado maneja muy bien la fontanería de los nodos vistos para nosotros.Y la versión de CPS nos muestra exactamente cómo será el flujo de control, de una manera mucho más explícita que la mónada de estado.
Oh, pero anyM
no parece estar en la biblioteca estándar. Esto es lo que parece:
anyM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool
anyM p [] = return False
anyM p (x:xs) = do
y <- p x
if y
then return True
else anyM p xs
@Daniel Gracias por notar el tipo ... simplemente lo escribí sin cortar de mi emacs y pegar aquí. (^. ^) –