好的Haskell编码风格的if / else控制块?
-
02-07-2019 - |
题
我正在学习Haskell,希望它能帮助我更接近函数式编程。以前,我主要使用的语言类似C语言,如C,Java和D.
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规范的下一个版本。
您还可以使用花括号进行显式分组。请参阅 http://www.haskell.org/tutorial/patterns.html的布局部分一>
我不建议这样做。在一些特殊情况下,我从未见过有人使用显式分组。我通常会查看标准前奏代码中的样式示例。
我使用的编码风格就像你在Wikibooks中的例子一样。当然,它不遵循C指南,但Haskell不是C,而且它相当可读,特别是一旦你习惯它。它也是在许多教科书中使用的算法风格之后形成的,例如Cormen。
你会看到一堆Haskell的不同缩进样式。如果没有一个设置为完全以任何风格缩进的编辑器,它们中的大多数都很难维护。
您显示的样式更简单,对编辑的要求也更低,我认为您应该坚持使用它。我可以看到的唯一不一致是你把第一个do放在它自己的行上,而把另一个dos放在then / else之后。
听取关于如何在Haskell中考虑代码的其他建议,但坚持你的缩进风格。