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

我对 if / else 控制块的编码风格有一点疑问/ wiki / Haskell / Simple_input_and_output“rel =”nofollow noreferrer“> 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语言中的推荐风格,我们应该缩进 if else if if 否则在同一列。

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

但是下面的风格怎么样?我认为它比上面的要清楚得多。但由于以上内容被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!"

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

有帮助吗?

解决方案

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

谓词(部分应用于 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

liftM 采用了纯粹的功能,并且提升了&#8220;它变成了monad。表达式的类型告诉我们它的作用很多:

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

这是一个I / O操作,运行时返回一个用读取转换的值,在我们的例子中是 Int 。回想一下, readLine 是一个产生 String 值的I / O操作,因此您可以将 liftM 视为允许我们应用读取&#8220;里面&#8221; IO monad。

示例游戏:

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

其他提示

您可以使用“案例”-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!"

对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的类型是什么?

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

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

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

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

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top