haskell - que - sentencia if en c++
¿Buen estilo de codificación Haskell del bloque de control if/else? (8)
Estoy aprendiendo que Haskell espera que me permita acercarme más a la programación funcional, antes de aprenderla, utilizo principalmente C-sytanx como los lenguajes, como C, Java o D Programming Language.
Seguí el tutorial en Wikilibro , va bien hasta ahora, pude entender la mayoría de ellos antes del capítulo "Entrada y salida simple"
Pero tengo una pequeña pregunta sobre el estilo de codificación del bloque de control if / else utilizado por el tutorial.
En wikibook, el código se parece a lo siguiente:
doGuessing num = do
putStrLn "Enter your guess:"
guess <- getLine
if (read guess) < num
then do putStrLn "Too low!"
doGuessing num
else if (read guess) > num
then do putStrLn "Too high!"
doGuessing num
else do putStrLn "You Win!"
Me hace confuso, porque este estilo de codificación es totalmente volador "Good Coding Style" en C-sytnax como lenguaje de programación, donde deberíamos identificar if / else if / else en la misma columna.
Sé que simplemente no funciona en Haskell, porque causaría un error de análisis si identifico "else" en la misma columna de "si".
Pero, ¿y el siguiente? Creo que es mucho más claro que el anterior. Pero como lo anterior es utilizado por Wikibook y Yet Another Haskell Tutorial, que marcó "el mejor tutorial disponible en línea" en el sitio oficial de Haskell, no estoy seguro de si este estilo de codificación es una convención en los programas de Haskell.
doGuessing num = do
putStrLn "Enter your guess:"
guess <- getLine
if (read guess) < num then
do
putStrLn "Too low!"
doGuessing num
else if (read guess) > num then do
putStrLn "Too high!"
doGuessing num
else do
putStrLn "You Win!"
Entonces, tengo curiosidad sobre qué estilo de codificación se usa con más frecuencia o si hay un estilo de codificación anthoer para este fragmento de código. Me gustaría saberlo también
¡El estilo Haskell es funcional, no imprescindible! En lugar de "hacer esto luego eso", piense en combinar funciones y describir lo que hará su programa, no cómo.
En el juego, su programa le pide al usuario una suposición. Una suposición correcta es un ganador. De lo contrario, el usuario intenta nuevamente. El juego continúa hasta que el usuario adivine correctamente, por lo que escribimos que:
main = untilM (isCorrect 42) (read `liftM` getLine)
Esto usa un combinador que ejecuta una acción repetidamente ( getLine
extrae una línea de entrada y read
convierte esa cadena a un entero en este caso) y verifica su resultado:
untilM :: Monad m => (a -> m Bool) -> m a -> m ()
untilM p a = do
x <- a
done <- p x
if done
then return ()
else untilM p a
El predicado (parcialmente aplicado en main
) verifica la conjetura contra el valor correcto y responde en consecuencia:
isCorrect :: Int -> Int -> IO Bool
isCorrect num guess =
case compare num guess of
EQ -> putStrLn "You Win!" >> return True
LT -> putStrLn "Too high!" >> return False
GT -> putStrLn "Too low!" >> return False
La acción que se ejecutará hasta que el jugador adivine correctamente es
read `liftM` getLine
¿Por qué no mantenerlo simple y simplemente componer las dos funciones?
*Main> :type read . getLine <interactive>:1:7: Couldn''t match expected type `a -> String'' against inferred type `IO String'' In the second argument of `(.)'', namely `getLine'' In the expression: read . getLine
El tipo de getLine
es IO String
, pero read
quiere una String
pura.
La función liftM
de Control.Monad toma una función pura y la "levanta" en una mónada. El tipo de expresión nos dice mucho sobre lo que hace:
*Main> :type read `liftM` getLine read `liftM` getLine :: (Read a) => IO a
Es una acción de E / S que cuando se ejecuta nos devuelve un valor convertido con read
, un Int
en nuestro caso. Recuerde que readLine
es una acción de E / S que produce valores de String
, por lo que puede pensar en que liftM
nos permite aplicar la read
"dentro" de la mónada IO
.
Juego de muestra:
1 Too low! 100 Too high! 42 You Win!
La forma en que Haskell interpreta if ... then ... else
dentro de un bloque do
está muy de acuerdo con la sintaxis de Haskell.
Pero muchas personas prefieren una sintaxis ligeramente diferente, permitiendo then
que aparezca en el mismo nivel de sangría que el if
correspondiente. Por lo tanto, GHC viene con una extensión de lenguaje DoAndIfThenElse
llamada DoAndIfThenElse
, que permite esta sintaxis.
La extensión DoAndIfThenElse
forma parte del lenguaje central en la última revisión de la especificación Haskell , Haskell 2010 .
Puedes usar la construcción "caso":
doGuessing num = do
putStrLn "Enter your guess:"
guess <- getLine
case (read guess) of
g | g < num -> do
putStrLn "Too low!"
doGuessing num
g | g > num -> do
putStrLn "Too high!"
doGuessing num
otherwise -> do
putStrLn "You Win!"
También puede usar la agrupación explícita con llaves. Consulte la sección de diseño de http://www.haskell.org/tutorial/patterns.html
No lo recomendaría sin embargo. Nunca he visto a nadie usar agrupación explícita además en algunos casos especiales. Normalmente miro el código del preludio estándar para ver ejemplos de estilo.
Tenga en cuenta que el hecho de que tenga que sangrar el ''entonces'' y ''else'' dentro de un bloque ''do'' se considera un error por muchos. Probablemente se solucionará en Haskell ''(Haskell prime), la próxima versión de la especificación Haskell.
Una mejora menor a la declaración de caso de mattiast (editaría, pero me falta el karma) es usar la función de comparación, que devuelve uno de los tres valores, LT, GT o EQ:
doGuessing num = do
putStrLn "Enter your guess:"
guess <- getLine
case (read guess) `compare` num of
LT -> do putStrLn "Too low!"
doGuessing num
GT -> do putStrLn "Too high!"
doGuessing num
EQ -> putStrLn "You Win!"
Realmente me gustan estas preguntas de Haskell, y animo a otros a publicar más. A menudo sientes que debe haber una mejor manera de expresar lo que estás pensando, pero Haskell es inicialmente tan extraño que no se te ocurrirá nada.
Pregunta extra para el periodista de Haskell: ¿cuál es el tipo de doGuessing?
Utilizo un estilo de codificación como tu ejemplo de Wikilibros. Claro, no sigue las pautas de C, pero Haskell no es C, y es bastante legible, especialmente una vez que te acostumbras. También está modelado según el estilo de los algoritmos utilizados en muchos libros de texto, como Cormen.
Verás un montón de diferentes estilos de sangría para Haskell. La mayoría de ellos son muy difíciles de mantener sin un editor que esté configurado para sangrar exactamente en cualquier estilo.
El estilo que muestra es mucho más simple y menos exigente para el editor, y creo que debería quedarse con él. La única incoherencia que puedo ver es que pones el primer do en su propia línea mientras pones el otro dos después del then / else.
Presta atención a los otros consejos sobre cómo pensar sobre el código en Haskell, pero mantén tu estilo de sangría.