2010-07-17 9 views
17

ACTUALIZACIÓN: Bien, esta pregunta se vuelve potencialmente muy simple.¿El mapa de Haskell no es flojo?

q <- mapM return [1..] 

¿Por qué nunca vuelve?


¿MapM no perezosamente se ocupa de las listas infinitas?

El código siguiente se cuelga. Sin embargo, si reemplazo la línea A por la línea B, ya no cuelga. Alternativamente, si precedí a la línea A con un "splitRandom $", tampoco se cuelga.

Q1 es: ¿MapM no es flojo? De lo contrario, ¿por qué el reemplazo de la línea A con la línea B "corrige este" código?

Q2 es: ¿Por qué la línea anterior A con splitRandom "resuelve" el problema?

import Control.Monad.Random 
import Control.Applicative 

f :: (RandomGen g) => Rand g (Double, [Double]) 
f = do 
    b <- splitRandom $ sequence $ repeat $ getRandom 
    c <- mapM return b -- A 
    -- let c = map id b -- B 
    a <- getRandom 
    return (a, c) 

splitRandom :: (RandomGen g) => Rand g a -> Rand g a 
splitRandom code = evalRand code <$> getSplit 

t0 = do 
    (a, b) <- evalRand f <$> newStdGen 
    print a 
    print (take 3 b) 

El código genera una lista infinita de números aleatorios de forma perezosa. Luego genera un solo número aleatorio. Al usar splitRandom, puedo evaluar este último número aleatorio primero antes de la lista infinita. Esto se puede demostrar si devuelvo b en lugar de c en la función.

Sin embargo, si aplico el mapM a la lista, el programa ahora se cuelga. Para evitar que esto se cuelgue, tengo que volver a aplicar splitRandom antes del mapM. Tenía la impresión de que mapM puede perezosamente

Respuesta

31

Bueno, hay flojera, y luego está flojo. mapM es de hecho flojo porque no hace más trabajo de lo necesario. Sin embargo, un vistazo a la firma de tipo:

mapM :: (Monad m) => (a -> m b) -> [a] -> m [b] 

Piense en lo que esto significa: usted le da una función a -> m b y un montón de a s. Un map normal puede convertirlos en un grupo de m b s, pero no un m [b]. La única forma de combinar el b en un solo [b] sin que la mónada se interponga es use >>= para secuenciar los m b juntos para construir la lista.

De hecho, mapM es exactamente equivalente a sequence . map.

En general, para cualquier expresión monádica, si se usa el valor, toda la cadena de >>= s que conduce a la expresión debe forzarse, por lo que la aplicación de sequence a una lista infinita no puede finalizar.

Si se desea trabajar con una secuencia de monádico sin límites, ya sea que necesitaremos control explícito flujo - por ejemplo, una condición de terminación de bucle al horno en la cadena de la liga de alguna manera, que las funciones recursivas simples como mapM y sequence no lo hacen proporcionar - o una secuencia paso a paso, algo como esto:

data Stream m a = Nil | Stream a (m (Stream m a)) 

... de modo que sólo la fuerza como muchas capas monad según sea necesario.

Editar :: En cuanto splitRandom, lo que está pasando allí es que estás pasando un cálculo Rand, evaluación que con la semilla splitRandom consigue, entonces return ing el resultado. Sin el splitRandom, la semilla utilizada por el único getRandom tiene que venir del resultado final de la secuencia de la lista infinita, por lo que se cuelga. Con el extra splitRandom, la semilla utilizada solo necesita pasar por las dos llamadas splitRandom, por lo que funciona. La lista final de números aleatorios funciona porque ha salido de la mónada Rand en ese punto y nada depende de su estado final.

+0

Pero esto no explica por qué antes de la línea con splitRandom se resuelve el problema.Además, tenga en cuenta que splitRandom realmente divide la semilla aleatoria en dos y usa una de ellas para generar la lista infinita, por lo que no debería haber una dependencia allí. – qrest

+0

@qrest: Se agregó una nota adicional - ¿eso ayuda? –

+0

@camccann: No, no es así. Aquí está mi razonamiento. Necesito consumir una semilla para generar la lista infinita. Por lo tanto, dividí la semilla y la usé en "splitRandom $ sequence $ repeat $ getRandom". NOTA: ¡Esta es una expresión monádica y no se evaluó por completo! Tenga en cuenta también que intencionalmente obtuve un número aleatorio para "a" DESPUÉS de la lista infinita, lo que demuestra que las semillas no son dependientes. Y la mónada Rand no usa la semilla a menos que se genere un número aleatorio para que no exista una dependencia de semilla. Entonces, ¿el "retorno" es el que realmente fuerza la evaluación? – qrest

7

Aquí hay un intento de prueba de que mapM return [1..] no finaliza. Supongamos por el momento que estamos en el Identity mónada (el argumento se aplicará a cualquier otra mónada igual de bien):

mapM return [1..] -- initial expression 
sequence (map return [1 ..]) -- unfold mapM 
let k m m' = m >>= \x -> 
      m' >>= \xs -> 
      return (x : xs) 
in foldr k (return []) (map return [1..]) -- unfold sequence 

Hasta aquí todo bien ...

-- unfold foldr 
let k m m' = m >>= \x -> 
      m' >>= \xs -> 
      return (x : xs) 
    go [] = return [] 
    go (y:ys) = k y (go ys) 
in go (map return [1..]) 

-- unfold map so we have enough of a list to pattern-match go: 
go (return 1 : map return [2..]) 
-- unfold go: 
k (return 1) (go (map return [2..]) 
-- unfold k: 
(return 1) >>= \x -> go (map return [2..]) >>= \xs -> return (x:xs) 

Recordemos que return a = Identity a en la mónada Identity y (Identity a) >>= f = f a en la mónada Identity. Continuando:

-- unfold >>= : 
(\x -> go (map return [2..]) >>= \xs -> return (x:xs)) 1 
-- apply 1 to \x -> ... : 
go (map return [2..]) >>= \xs -> return (1:xs) 
-- unfold >>= : 
(\xs -> return (1:xs)) (go (map return [2..])) 

Tenga en cuenta que en este momento nos encantaría aplicará a \xs, pero todavía no puedo! Tenemos que seguir desarrollando su lugar hasta que tengamos un valor de aplicar:

-- unfold map for go: 
(\xs -> return (1:xs)) (go (return 2 : map return [3..])) 
-- unfold go: 
(\xs -> return (1:xs)) (k (return 2) (go (map return [3..]))) 
-- unfold k: 
(\xs -> return (1:xs)) ((return 2) >>= \x2 -> 
         (go (map return [3..])) >>= \xs2 -> 
         return (x2:xs2)) 
-- unfold >>= : 
(\xs -> return (1:xs)) ((\x2 -> (go (map return [3...])) >>= \xs2 -> 
         return (x2:xs2)) 2) 

En este punto, todavía no se puede aplicar a \xs, pero podemos aplicar a \x2. Continuando:

-- apply 2 to \x2 : 
(\xs -> return (1:xs)) ((go (map return [3...])) >>= \xs2 -> 
         return (2:xs2)) 
-- unfold >>= : 
(\xs -> return (1:xs)) (\xs2 -> return (2:xs2)) (go (map return [3..])) 

Ahora hemos llegado a un punto donde ni \xs\xs2 ni se puede reducir todavía! Nuestra única opción es:

-- unfold map for go, and so on... 
(\xs -> return (1:xs)) 
    (\xs2 -> return (2:xs2)) 
    (go ((return 3) : (map return [4..]))) 

Así se puede ver que, debido a foldr, estamos construyendo una serie de funciones para aplicar, a partir del final de la lista y trabajando nuestro camino de vuelta. Debido a que en cada paso la lista de entrada es infinita, este despliegue nunca terminará y nunca obtendremos una respuesta.

Esto tiene sentido si nos fijamos en este ejemplo (tomado de otro hilo de StackOverflow, no puedo encontrar cuál en este momento).En la siguiente lista de mónadas:

mebs = [Just 3, Just 4, Nothing] 

que esperaría sequence para coger el Nothing y devolver un fracaso para todo el asunto:

sequence mebs = Nothing 

Sin embargo, para esta lista:

mebs2 = [Just 3, Just 4] 

esperaríamos que la secuencia nos diera:

sequence mebs = Just [3, 4] 

En otras palabras, sequencetiene para ver toda la lista de cómputos monádicos, encadenarlos y ejecutarlos todos para obtener la respuesta correcta. No hay manera de que sequence pueda dar una respuesta sin ver toda la lista.

Nota: La versión anterior de esta respuesta afirmó que foldr calcula a partir de la parte posterior de la lista, y no funcionaría en absoluto en las listas infinitas, pero eso es incorrecto! Si el operador que pasa al foldr es flojo en su segundo argumento y produce salida con un constructor de datos perezoso como una lista, foldr trabajará felizmente con una lista infinita. Ver foldr (\x xs -> (replicate x x) ++ xs) [] [1...] para un ejemplo. Pero ese no es el caso con nuestro operador k.

+1

diciendo que 'foldr' funciona" desde la parte posterior de la lista "es un poco confuso. Tanto 'foldl' como' foldr' procesan la lista de izquierda a derecha. La diferencia es * horquillado *, no la dirección de procesamiento. – nh2

+1

Yo diría que su descripción es la que es más confusa. La documentación de Prelude en sí dice que 'foldr'" reduce la lista usando el operador binario, de derecha a izquierda "- ¡el horquillado es el punto! El pase inicial de izquierda a derecha a través de la lista (requerido de todos modos para recorrer la estructura de la lista de Haskell) establece la pila de recursión, pero no computa los elementos de la lista. –

+0

No estoy de acuerdo: no hay un "pase inicial a la derecha" hasta el final. Si hubo, ¿cómo podría 'foldr' trabajar en listas infinitas? 'foldr (:) [] [1 ..]' funciona perfectamente bien y es memoria constante; esto no podría ser si la lista se procesara de derecha a izquierda porque no hay un extremo derecho. – nh2

7

Bien, esta pregunta se vuelve potencialmente muy simple.

q < - mapm retorno [1 ..]

¿Por qué esto no volver nunca?

No es necesariamente cierto. Depende de la mónada estás en

Por ejemplo, con la mónada de identidad, se puede utilizar el resultado con pereza y se termina bien:.

newtype Identity a = Identity a 

instance Monad Identity where 
    Identity x >>= k = k x 
    return = Identity 

-- "foo" is the infinite list of all the positive integers 
foo :: [Integer] 
Identity foo = do 
    q <- mapM return [1..] 
    return q 

main :: IO() 
main = print $ take 20 foo -- [1 .. 20] 
1

Hablemos de esto en un contexto más genérico.

Como dicen las otras respuestas, el mapM es solo una combinación de sequence y map. Entonces, el problema es por qué sequence es estricto en ciertos Monad s. Sin embargo, esto no está restringido a Monads sino también a Applicative s ya que tenemos sequenceA que comparten la misma implementación de sequence en la mayoría de los casos.

Ahora mira el (especializada para las listas) tipo de firma de sequenceA:

sequenceA :: Applicative f => [f a] -> f [a] 

¿Cómo hacer esto? Le dieron una lista, por lo que le gustaría usar foldr en esta lista.

sequenceA = foldr f b where ... 
    --f :: f a -> f [a] -> f [a] 
    --b :: f [a] 

Desde f es un Applicative, ya sabes lo que sea b coule - pure []. ¿Pero qué es f? Obviamente se trata de una versión elevada de (:):

(:) :: a -> [a] -> [a] 

Así que ahora sabemos cómo sequenceA obras:

sequenceA = foldr f b where 
    f a b = (:) <$> a <*> b 
    b = pure [] 

o

sequenceA = foldr ((<*>) . fmap (:)) (pure []) 

Supongamos que se les dio una lista perezosa (x:_|_). La definición anterior de sequenceA da

sequenceA (x:_|_) === (:) <$> x <*> foldr ((<*>) . fmap (:)) (pure []) _|_ 
        === (:) <$> x <*> _|_ 

Así que ahora vemos el problema se reduce a considerar es el tiempo f <*> _|__|_ o no. Obviamente, si f es estricto, esto es _|_, pero si f no es estricto, para permitir una detención de la evaluación, se requiere que <*> sea no estricto.

Así que los criterios para un funtor aplicativo que tienen un sequenceA que para el será el operador <*> a ser no estricto. Una prueba simple sería

const a <$> _|_ === _|_  ====> strict sequenceA 
-- remember f <$> a === pure f <*> a 

Si estamos hablando de Moand s, el criterio es

_|_ >> const a === _|_ ===> strict sequence 
3

Esta pregunta está mostrando muy bien la diferencia entre la mónada IO y otras mónadas. En segundo plano, el mapaM crea una expresión con una operación de vinculación (>> =) entre todos los elementos de la lista para convertir la lista de expresiones monádicas en una expresión monádica de una lista. Ahora, lo que es diferente en la mónada IO es que el modelo de ejecución de Haskell está ejecutando expresiones durante el enlace en la Mónada IO. Esto es exactamente lo que finalmente obliga (en un mundo puramente perezoso) a ejecutar algo.

Así que IO Monad es especial en cierto modo, está usando el paradigma de secuencia de bind para hacer cumplir realmente la ejecución de cada paso y esto es lo que nuestro programa hace para ejecutar cualquier cosa al final. Otras Mónadas son diferentes. Tienen otros significados del operador de enlace, dependiendo de la Mónada. IO es en realidad la única Mónada que ejecuta cosas en el enlace y esta es la razón por la cual los tipos IO son "acciones".

El siguiente ejemplo muestra que otras Mónadas no imponen la ejecución, la Mónada Maybe por ejemplo. Finalmente, esto da como resultado que un mapM en IO Monad devuelve una expresión que, cuando se ejecuta, ejecuta cada elemento individual antes de devolver el valor final.

Hay buenos artículos acerca de esto, empieza aquí o buscar la semántica denotativa y mónadas: Hacer frente al pelotón de los torpes: http://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/mark.pdf

Ejemplo con Tal Mónada:

módulo principal, donde

fstMaybe: : [Int] -> Maybe [Int] fstMaybe = mapM (\ x -> if x == 3 then Nothing else Just x)

main = do let r = fstMaybe [1 ..] return r

Cuestiones relacionadas