2011-12-26 15 views
7

Estoy leyendo a través de LYAH, y en el capítulo 9, encontré un problema curioso. El autor ofrece un ejemplo de la aplicación de la función "azares":Haskell: Implementar "randoms" (a.k.a., variable de tipo ambiguo)

randoms' :: (RandomGen g, Random a) => g -> [a] 
randoms' gen = let (value, newGen) = random gen in value:randoms' newGen 

Bueno, esto compila bien. Pero si cambio la segunda línea a:

randoms' gen = (fst (random gen)) : (randoms' (snd (random gen))) 

El este error informes de archivos de carga:

IOlesson.hs:4:52: 
    Ambiguous type variable `a' in the constraint: 
     `Random a' arising from a use of `random' at IOlesson.hs:4:52-61 
    Probable fix: add a type signature that fixes these type variable(s) 
Failed, modules loaded: none. 

Si cambio de esta línea a:

randoms' gen = (fst (random gen)) : (randoms' gen) 

entonces esto va a hacer precisamente bien, y como se esperaba, esto devolverá una lista de todos los elementos idénticos.

Estoy confundido: ¿Qué hay de diferente en la versión de Miran y en mi versión?

¡Gracias por cualquier idea!

Respuesta

7

El problema es que random toma cualquier instancia de RandGen, y devuelve un valor aleatorio y un generador nuevo del mismo tipo. ¡Pero el valor aleatorio puede ser cualquier tipo con una instancia de Random!

random :: (Random a, RandomGen g) => g -> (a, g) 

Por lo tanto, cuando se llama random por segunda vez en la recursividad, que no sabe cuál es el tipo del primer elementodebe ser! Es cierto que realmente no te importa (lo tiras con snd, después de todo), pero la elección de a puede afectar el comportamiento de random. Para desambiguar, debe decirle a GHC lo que quiere que sea a. La forma más sencilla es volver a escribir su definición como sigue:

randoms' gen = let (value, gen') = random gen in value : randoms' gen' 

Debido a que se utiliza value como parte de la lista resultante, que está obligado a tener el mismo tipo que el un en su firma Tipo - el tipo de elemento de la lista resultante. Se resuelve la ambigüedad y se evita el cálculo duplicado del siguiente número aleatorio para arrancar. Hay formas de desambiguar esto de forma más directa (manteniendo el cálculo duplicado), pero son feos o confusos o implican extensiones de lenguaje. Afortunadamente, no debería encontrarse con esto muy a menudo, y cuando lo haga, un método como este debería funcionar para resolver la ambigüedad.

De manera equivalente y tal vez más claramente, se puede escribir:

randoms' gen = value : randoms' gen' 
    where (value, gen') = random gen 
+0

Gracias! Esto es tan contra intuitivo, pero totalmente comprensible. –

+0

De nada; los errores de ambigüedad de tipo de letra pueden ser complicados al principio, pero debe entenderlos pronto :) – ehird

4

considerar el tipo de random:

random :: (RandomGen g, Random a) => g -> (a, g) 

La tupla resultado consiste en un valor de cualquier tipo que es una instancia de Random, y el valor de RNG actualizado. La parte importante es "cualquier instancia": nada requiere ambos usos de random para producir un valor del mismo tipo.

En fst (random gen) no hay ningún problema, porque el valor generado es el que necesita la función completa; en snd (random gen), sin embargo, el valor aleatorio se descarta, por lo que se desconoce por completo qué tipo debería ser. Sin saber el tipo, Haskell no puede elegir la instancia de Random adecuada para utilizar, de ahí el error de tipo ambiguo que ve.

1

random es de tipo: RandomGen g => g -> (a, g)

y por lo tanto snd (random gen) es solamente de tipo g -> g. Y luego no sabe qué es a. Existe un random diferente para cada tipo de datos que desee generar, pero en este caso el compilador no sabe si desea random :: g -> (Int,g) o random :: g->(Char,g) u otra cosa.

Esto explica whey (value, newGen) = random gen funciona. Ayuda al compilador a unir su conocimiento de lo que es a. value debe ser del tipo a y, por lo tanto, puede deducir el tipo de random gen.

(Editado:. He eliminado un intento incorrecto que hice en fijándolo Sólo se adhieren con el código original en la cuestión!)

+1

Esa corrección requiere '{- # LANGUAGE ScopedTypeVariables # -}' y un 'forall' explícito en la firma de tipo. – ehird

+0

(Estoy lejos de ser un experto en los detalles de qué es una extensión y qué no) Me sorprende que @ehird, la variable de tipo 'a' ya está en la firma de tipo. Solo estoy usando esto para especificar claramente el tipo de expresión 'random gen'. Hubiera pensado que esto era muy estándar Haskell. –

+0

En Haskell estándar, cada vez que utiliza una variable de tipo después de '::' está en un nuevo ámbito. Es muy tonto – ehird