English 中文(简体)
if/else控制块的Haskell编码风格好吗?
原标题:
  • 时间:2008-09-24 13:40:35
  •  标签:

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

我对关于Wikibooks的教程。代码如下:

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 ifothers

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

但是下面的风格呢?我认为这比上面的要清楚得多。但由于以上内容被Wikibooks和另一个Haskell教程使用,该教程在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!"

所以,我很好奇哪种编码风格更常用,或者这段代码有其他编码风格吗?

最佳回答

Haskell风格是功能性的,而不是命令性的!与其“先做这个再做那个”,不如考虑组合函数并描述程序将做什么,而不是如何做。

在游戏中,您的程序会要求用户进行猜测。正确的猜测是赢家。否则,用户将重试。游戏继续进行,直到用户猜对为止,所以我们写道:

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

这使用了一个重复运行操作的组合子(在这种情况下,getLine拉一行输入,读取将该字符串转换为整数),并检查其结果:

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

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

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字符串,但read想要一个纯的String

函数liftM来自Control。Monad接受一个纯函数并将其“提升”为Monad。表达式的类型告诉了我们很多关于它的功能:

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

这是一个I/O操作,当运行时,它会给我们返回一个用read转换的值,在我们的情况下是一个Int。回想一下,readLine是一个产生String值的I/O操作,因此您可以将liftM视为允许我们在IOmonad“内部”应用read

示例游戏:

1
Too low!
100
Too high!
42
You Win!
问题回答

您可以使用“case”构造:

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

mattiast的case语句(我可以编辑,但我没有因果报应)的一个小改进是使用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记者的额外问题:doGuessing是什么类型的?

Haskell解释<code>的方式如果。。。然后do块中的else与Haskell的整个语法非常一致。

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

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

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

您也可以使用带大括号的显式分组。请参阅http://www.haskell.org/tutorial/patterns.html

不过我不建议这么做。除了在一些特殊情况下,我从未见过有人使用显式分组。我通常会查看标准前奏曲代码,以获取风格示例。

我使用了一种编码风格,就像你在Wikibooks中的例子一样。当然,它没有遵循C准则,但Haskell不是C,而且它很可读,尤其是当你习惯了它的时候。它还模仿了许多教科书中使用的算法风格,比如Cormen。

您将看到Haskell的一系列不同的缩进样式。如果没有设置为以任何样式缩进的编辑器,它们中的大多数都很难维护。

你显示的风格要简单得多,对编辑器的要求也更低,我认为你应该坚持下去。我能看到的唯一不一致之处是,你把第一个do放在自己的行上,而把其他do放在then/else之后。

注意其他关于如何思考Haskell中的代码的建议,但要坚持你的缩进风格。





相关问题