Boa Haskell estilo de codificação de if / bloco de controle mais?
-
02-07-2019 - |
Pergunta
Estou aprendendo Haskell na esperança de que ele vai me ajudar a chegar mais perto de programação funcional. Anteriormente, eu principalmente idiomas usado com C-como sintaxe, como C, Java, e D.
Eu tenho uma pequena dúvida sobre o estilo de codificação de um bloco de controle if
/ else
usado pelo tutorial sobre Wikibooks . Os olhares código como o seguinte:
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!"
Isso me deixa confuso, porque este estilo de codificação viola totalmente o estilo recomendado em C-como línguas, onde devemos endentar if
, else if
e else
na mesma coluna.
Eu sei que simplesmente não funciona em Haskell, porque seria um erro de análise se eu recuado else
na mesma coluna que if
.
Mas o que dizer o seguinte estilo? Eu acho que é muito mais claro do que o descrito acima. Mas desde que o acima é usada por Wikibooks e ainda um outro Haskell Tutorial, que é marcado "melhor tutorial disponível on-line" no site oficial Haskell, eu não tenho certeza se este estilo de codificação é uma convenção em programas 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!"
Então, estou curioso sobre qual estilo de codificação é usado mais frequentemente ou existe um outro estilo de codificação para este pedaço de código?
Solução
estilo Haskell é funcional, não é imperativo! Ao invés de "fazer isso, então, que," pensar sobre como combinar funções e descrevendo o seu programa vai fazer, não como.
No jogo, o programa pede ao usuário para uma suposição. Um palpite correto é um vencedor. Caso contrário, o usuário tenta novamente. O jogo continua até que as suposições de usuário corretamente, então nós escrevemos que:
main = untilM (isCorrect 42) (read `liftM` getLine)
Isto usa um combinadores que repetidamente executa uma acção (getLine
puxa uma linha de entrada e convertidos read
que a cadeia de um número inteiro, neste caso) e verifica o 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
O predicado (parcialmente aplicado em main
) verifica a suposição contra o valor correcto e responde em conformidade:
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
A ação a ser executado até que o jogador adivinha corretamente é
read `liftM` getLine
Por que não mantê-lo simples e apenas compor as duas funções?
*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
O tipo de getLine
é IO String
, mas read
quer um String
puro.
A função liftM
partir Control.Monad leva uma função pura e “elevadores”-lo em uma mônada. O tipo da expressão nos diz muito sobre o que ele faz:
*Main> :type read `liftM` getLine read `liftM` getLine :: (Read a) => IO a
É uma ação de I / O que nos quando executado devolve um valor convertido com read
, um Int
no nosso caso. Lembre-se que readLine
é uma ação de I / O que os rendimentos String
valores, de modo que você pode pensar liftM
como o que nos permite aplicar read
“dentro” da mônada IO
.
jogo Amostra:
1 Too low! 100 Too high! 42 You Win!
Outras dicas
Você pode usar o "caso" -construct:
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!"
Uma pequena melhoria a declaração caso de mattiast (eu editar, mas me falta a karma) é usar a função de comparação, que retorna um dos três valores, LT, GT, ou 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!"
Eu realmente gosto dessas perguntas Haskell, e eu incentivar outras pessoas a postar mais. Muitas vezes você se sente como se houvesse tem para ser uma maneira melhor de expressar o que você está pensando, mas Haskell é inicialmente tão estranho que nada virá à mente.
Bônus pergunta para a journyman Haskell: qual é o tipo de doGuessing
A forma Haskell interpreta if ... then ... else
dentro de um bloco do
está muito em sintonia com toda a sintaxe do Haskell.
Mas muitas pessoas preferem uma sintaxe ligeiramente diferente, permitindo then
e else
a aparecer ao mesmo nível de recuo como o if
correspondente. Portanto, GHC vem com um opt-in de extensão linguagem chamada DoAndIfThenElse
, que permite que esta sintaxe.
A extensão DoAndIfThenElse
é feita em parte da linguagem núcleo na última revisão da especificação Haskell, Haskell 2010 .
Note que o fato de que você tem que recuar o 'depois' e 'else' dentro de um bloco 'fazer' é considerado um bug por muitos. Ele provavelmente será corrigido em Haskell'(Haskell prime), a próxima versão da especificação Haskell.
Você também pode usar agrupamento explícita com chaves. Consulte a seção layout do http://www.haskell.org/tutorial/patterns.html
Eu não recomendaria que embora. Eu nunca vi ninguém uso agrupamento explícita além de em alguns casos especiais. Eu costumo olhar para o Padrão Prelude código para exemplos de estilo.
Eu uso um estilo de codificação como o seu exemplo de Wikibooks. Claro, ele não seguir as orientações C, mas Haskell não é C, e é bastante legível, especialmente uma vez que você se acostumar com isso. Ele também é modelada após o estilo de algoritmos usados ??em muitos livros didáticos, como Cormen.
Você vai ver um monte de diferentes estilos de recuo para Haskell. A maioria deles são muito difíceis de manter sem um editor que está configurado para travessão exatamente em qualquer estilo.
O estilo que você exibir é muito mais simples e menos exigente do editor, e eu acho que você deveria ficar com ela. A única inconsistência eu posso ver é que você colocar a primeira fazer em sua própria linha enquanto você colocar os outros dos após o then / else.
Preste atenção a outros conselhos sobre a forma de pensar sobre o código em Haskell, mas vara para o seu estilo de recuo.