Translate

martes, 17 de febrero de 2015

Aleatoriedad en Haskell


Muchas veces necesitamos un numero aleatorio, tanto para hacer un juego o para simulación. En lenguajes imperativos esto es muy fácil, podemos utilizar una función que nos devuelva un numero aleatorio. Pero en la programación funcional, esto no es tan fácil, dado que la programación funcional se sostiene en un pilar que se llama "transparencia referencial"

La transparencia referencial es muy importante, lo que dice es que todas las funciones debe devolver el siempre mismo valor dado un parámetro determinado. Esto no siempre es así en la programación imperativa porque podemos tener estados, y esos estados pueden cambiar el comportamiento de una función, les muestro un ejemplo para ser más claro:

var a = 2;
var c = 3;

function ejemplo(paremetro) {
     if (a == 2) {
         return ++parametro;
     }
return --parametro;
}

> ejemplo(a)
3
> ejemplo(2)
1

La función utiliza una variable global, y dado el estado de esa variable incrementa el parámetro o lo decrementa.

Bueno, recuerda, Haskell es un lenguaje funcional puro. Esto hace un poco complicado obtener números aleatorios.

Si hacemos la función:

randomNumber :: (Num a) => a
randomNumber = 4

No será muy útil como función de números aleatorios ya que siempre nos devolverá el mismo 4, aunque puedo asegurar que ese 4 es totalmente aleatorio ya que acabo de lanzar un dado para obtenerlo.

Esto me hace acordar un chiste:


¿Qué hacen demás lenguajes para generar número aparentemente aleatorios? Bueno, primero obtienen algunos datos de tu computadora, como la hora actual, cuanto y a donde has movido el ratón, el ruido que haces delante del computador, etc. Y basándose en eso, devuelve un número que parece aleatorio. La combinación de esos factores (la aleatoriedad) probablemente es diferente en cada instante de tiempo, así que obtienes números aleatorios diferentes.

Así que en Haskell, podemos crear un número aleatorio si creamos una función que tome como parámetro esa aleatoriedad y devuelva un número (o cualquier otro tipo de dato) basándose en ella.

Utilizaremos el módulo System.Random. Contiene todas las funciones que calmaran nuestra sed de aleatoriedad. Vamos a jugar con una de las funciones que exporta, llamada random. Su declaración de tipo es random :: (RandomGen g, Random a) => g -> (a, g) ¡Wau! Hay nuevas clases de tipos en esta declaración. La clase de tipos RandomGen es para tipos que pueden actuar como fuentes de aleatoriedad. La clase de tipos Random es para tipos que pueden tener datos aleatorios. Un dato booleano puede tener valores aleatorios, True o False. Un número también puede tomar un conjunto de diferentes valores alotarios ¿Puede el tipo función tomar valores aleatorios? No creo. Si traducimos la declaración de tipo de random al español temos algo como: toma un generador aleatorio (es decir nuestra fuente de aleatoriedad) y devuelve un valor aleatorio y un nuevo generador aleatorio ¿Por qué devuelve un nuevo generador junto al valor aleatorio? Lo veremos enseguida.

Para utilizar la función random, primero tenemos que obtener uno de esos generadores aleatorios. El módulo System.Random exporta un tipo interensante llamado StdGen que posee una instancia para la clase de tipos RandomGen. Podemos crear un StdGen manualmente o podemos decirle al sistema que nos de uno basandose en un motón de cosas aleatorias.

Para crear manualmente un generador aletario, utilizamos la función mkStdGen. Tiene el tipo Int -> StdGen. Toma un entero y basándose en eso, nos devuelve un generador aleatorio. Bien, vamos a intentar utilizar el tandem random mkStdGen para obtener un número aleatorio.

ghci> random (mkStdGen 100)
<interactive>:1:0:
    Ambiguous type variable `a' in the constraint:
      `Random a' arising from a use of `random' at <interactive>:1:0-20
    Probable fix: add a type signature that fixes these type variable(s)

¿Qué pasa? Ah, la función random puede devolver cualquier tipo que sea miembro de la clase de tipos Random, así que tenemos que decir a Haskell exactamente que tipo queremos. Recuerda también que devuelve un valor aleatorio y un generador.

ghci> random (mkStdGen 100) :: (Int, StdGen)
(-1352021624,651872571 1655838864)

¡Por fin, un número que parece aleatorio! El primer componente de la dupla es nuestro número aleatorio mientras que el segundo componente es una representación textual del nuevo generador ¿Qué sucede si volvemos a llamar random con el mismo generador?

ghci> random (mkStdGen 100) :: (Int, StdGen)
(-1352021624,651872571 1655838864)

Por supuesto. El mismo resultado para los mismos parámetros. Vamos a probar dándole como parámetro un generador diferente.

ghci> random (mkStdGen 949494) :: (Int, StdGen)
(539963926,466647808 1655838864)

Genial, un número diferente. Podemos usar la anotación de tipo con muchos otros tipos.

ghci> random (mkStdGen 949488) :: (Float, StdGen)
(0.8938442,1597344447 1655838864)
ghci> random (mkStdGen 949488) :: (Bool, StdGen)
(False,1485632275 40692)
ghci> random (mkStdGen 949488) :: (Integer, StdGen)
(1691547873,1597344447 1655838864)

Esto es muy bueno!!

Dejo la fuente: http://aprendehaskell.es/content/EntradaSalida.html#aleatoriedad