Práctico Haskell 4: Mónadas, QuickCheck, HUnit

Preparación

Mónadas Maybe y listas

Acabamos de aprender sobre mónadas y comprensiones de listas. Los ejercicios que siguien son una oportunidad para ver que hemos aprendido. Son relativamente simples.

Ejercicio 1

Escribí una función que detecta si una String tiene cierto formato. El formato requerido es el siguiente:

  1. La string empieza con un dígito.
  2. Llamemos n el valor de ese dígito. La string contiene luego n caracteres 'a'.
  3. Después de las 'a', o la string se termina, o la secuencia se repite, empezando con un dígito (puede ser distinto).

Ejemplos:

Buenas strings Malas strings
3aaa2aa 3aaa2a
9aaaaaaaaa 10aaaaaaaaaa
0 1
001a 100a
2aa2aa 2bb2bb

Tu función debe usar la mónada Maybe. Debería parecerse a esto:

stringFitsFormat :: String -> Bool
stringFitsFormat = isJust . go
  where go :: String -> Maybe String
        -- go evalua a Just "" si es exitosa, sino a Nothing
        ...

Ayuda: usá readMaybe :: Read a => String -> Maybe a de Text.Read y stripPrefix :: Eq a => [a] -> [a] -> Maybe [a] de Data.List.

Ejercicio 2

Usá una comprensión de lista para producir la lista de todos los números entre 1 y 100 (incluso) que son divisibles por 5 pero no por 7.

specialNumbers :: [Int]

QuickCheck y HUnit: verificando anillos

QuickCheck te da el poder increible de hacer tests aleatorios. Todo lo que hay que hacer es escribir propiedades generales para testear, y dejar que QuickCheck haga la generación de casos de tests. En nuestro caso, vamos a hacer tests para asegurarnos que las instancias de Ring cumplan con las propiedades de los anillos.

Ejercicio 3

Si querés verificar los anillos, vas a necesitar instancias Arbitrary para Mod5 y Mat2x2 para que QuickCheck pueda crear valores arbitrarios para esos tipos. Averiguá la documentación de la clase Arbitrary. Vas a ver la función arbitrary :: Gen a, esta es la que debés implementar.

Fijate en la parte de la documentación que se llama “Random generation”, en particular “generator combinators”. Estos combinadores van a ser útiles. Lo más importante, es que Gen es instancia de la clase Monad, o sea, ¡es una mónada! Entonces podés empezar con arbitrary = do ... y seguir así.

Escribí instancias Arbitrary para Mod5 y Mat2x2.

Ayuda: Integer ya tiene instancias para Arbitrary y Random.

http://hackage.haskell.org/package/QuickCheck-2.7.6/docs/Test-QuickCheck.html

Podrás probar tus instancias en GHCi evaluando las expresiones siguientes:

Ejercicio 4

Implementá el método shrink para Mat2x2 leyendo la documentación de shrink.

Ejercicio 5

Leé la página de Wikipedia sobre anillos.

Un poco más abajo en la página, vas a ver las 9 propiedades que un anillo debe tener. Codificá esas propiedades de forma que se puedan usar con QuickCheck. Para ser compatible con nuestro test automático, nombralos desde prop_1 hasta prop_9.

Asegurate que tus propiedades anden con quickCheck ejecutando, por ejemplo, quickCheck prop_1 en GHCi. Si querés testear otra cosa que Integer, tenés que agregar una anotación de tipo, como por ejemplo quickCheck (prop_1 :: Mat2x2 -> Mat2x2 -> Bool).

Ejercicio 6

Escribí una propiedad para gobernarlos a todos, llamada prop_ring, que averigua si todas las propiedades del anillo se cumplen. Hay varias formas de hacer eso, algunas más limpias que otras. Usá combinadores útiles, como conjoin y .&&.. Cuidado que los tipos de esos combinadores son un poco desafiantes.

(También es posible escribir prop_ring sin usar esos combinadores, pero no es tan lindo ni divertido.)

Ejercicio 7

Uno de los anillos definidos en Ring.hs está roto. Utiliza tus tests para encontrar cuál, y escribí tu descubrimiento como comentario en tu código.

Ejercicio 8

Hay instancias de Parsable en Ring.hs. Son más difíciles de comprobar usando tests basados sobre propiedades, entonces vamos a usar tests unitarios con HUnit.

Escribí un test

parserTests :: Test
parserTests = TestList [ ... ]

que incluya por lo menos dos tests para cada instancia de Parsable. Asegurate de dar nombres descriptivos a cada test usando (~:). Podés ayudarte con la función parseAll, que parsea suponiendo que toda la string se consume - es decir, comprueba si no queda nada despues de parsear.

Ayuda: para comprobar la instancia Integer Parsable, probablemente vas a necesitar una anotación de tipo para aclarar que querés Integer y no, por ejemplo, Int. Por ejemplo, puede ser que necesites escribir algo como:

parseAll "3" ~?= Just (3 :: Integer)

Fuente