我正在学习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语言中的推荐风格,我们应该在同一列中缩进if
,else if
和else
。
我知道它在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!"
所以,我很想知道哪种编码风格更经常使用 - 或者这段代码是否有其他编码风格?
答案 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的整个语法。
但许多人更喜欢略有不同的语法,允许then
和else
出现在与相应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中考虑代码的其他建议,但坚持你的缩进风格。