好的Haskell编码风格的if / else控制块?

时间:2008-09-24 13:40:36

标签: haskell coding-style

我正在学习Haskell,希望它能帮助我更接近函数式编程。以前,我主要使用的语言类似C语言,如C,Java和D.

我对tutorial on Wikibooks使用的if / else控制块的编码风格有一点疑问。代码如下所示:

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!"

这让我很困惑,因为这种编码风格完全违反了C语言中的推荐风格,我们应该在同一列中缩进ifelse ifelse

我知道它在Haskell中不起作用,因为如果我在else的同一列缩进if,那将是一个解析错误。

但是下面的风格怎么样?我认为它比上面的要清楚得多。但由于以上内容被Wikibooks和Yet Another Haskell Tutorial(在Haskell官方网站上标记为“最佳在线教程”)使用,我不确定这种编码风格是否是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!"

所以,我很想知道哪种编码风格更经常使用 - 或者这段代码是否有其他编码风格?

8 个答案:

答案 0 :(得分:27)

Haskell风格功能齐全,不是必须的!而不是“那样做”,考虑组合函数和描述你的程序将做什么,而不是如何。

在游戏中,您的程序会要求用户猜测。正确的猜测是赢家。否则,用户再次尝试。游戏一直持续到用户猜对了,所以我们写下:

main = untilM (isCorrect 42) (read `liftM` getLine)

这使用一个重复运行一个动作的组合子(getLine拉一行输入,read将该字符串转换为整数,并检查其结果:

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

谓词(部分应用于main)会根据正确的值检查猜测并做出相应的响应:

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

在玩家正确猜测之前要执行的操作是

read `liftM` getLine

为什么不保持简单,只需要编写这两个函数?

*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

getLine的类型为IO String,但read需要纯String

Control.Monad中的函数liftM采用纯函数并将其“提升”为monad。表达式的类型告诉我们它的作用很多:

*Main> :type read `liftM` getLine
read `liftM` getLine :: (Read a) => IO a

这是一个I / O操作,在运行时会返回一个用read转换的值,在我们的例子中为Int。回想一下,readLine是一个产生String值的I / O操作,因此您可以将liftM视为允许我们在read内部应用IO } monad。

示例游戏:

1
Too low!
100
Too high!
42
You Win!

答案 1 :(得分:8)

你可以使用“case”--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!"

答案 2 :(得分:8)

对mattiast案例陈述的一个小改进(我编辑,但我缺乏业力)是使用compare函数,它返回三个值中的一个,LT,GT或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!"

我非常喜欢这些Haskell问题,我鼓励其他人发布更多信息。通常你觉得 是一种更好的方式来表达你的想法,但Haskell最初是如此陌生以至于没有任何想法会被想到。

Haskell journyman的奖金问题:doGuessing的类型是什么?

答案 3 :(得分:4)

Haskell在if ... then ... else块中解释do的方式非常符合Haskell的整个语法。

但许多人更喜欢略有不同的语法,允许thenelse出现在与相应if相同的缩进级别。因此,GHC附带了一个名为DoAndIfThenElse的选择加入语言扩展,它允许这种语法。

DoAndIfThenElse扩展在Haskell规范的最新版本Haskell 2010中成为核心语言的一部分。

答案 4 :(得分:3)

请注意,您必须在'do'块中缩进'then'和'else'这一事实被许多人认为是一个错误。它可能会在Haskell(Haskell prime)中修复,这是Haskell规范的下一个版本。

答案 5 :(得分:1)

您还可以使用花括号进行显式分组。请参阅http://www.haskell.org/tutorial/patterns.html

的布局部分

我不建议这样做。在一些特殊情况下,我从未见过有人使用显式分组。我通常会查看Standard Prelude code的样式示例。

答案 6 :(得分:0)

我使用的编码风格就像你在Wikibooks中的例子一样。当然,它不遵循C指南,但Haskell不是C,而且它相当可读,特别是一旦你习惯它。它也是在许多教科书中使用的算法风格之后形成的,例如Cormen。

答案 7 :(得分:0)

你会看到一堆Haskell的不同缩进样式。如果没有一个设置为完全以任何风格缩进的编辑器,它们中的大多数都很难维护。

您显示的样式更简单,对编辑的要求也更低,我认为您应该坚持使用它。我可以看到的唯一不一致是你把第一个do放在它自己的行上,而把另一个dos放在then / else之后。

听取关于如何在Haskell中考虑代码的其他建议,但坚持你的缩进风格。