[这个问题的动机来自“真实世界Haskell”中的第9章]
这是一个简单的功能(缩减为必需品):
saferOpenFile path = handle (\_ -> return Nothing) $ do
h <- openFile path ReadMode
return (Just h)
为什么我需要$
?
如果要处理的第二个参数不是do块,我不需要它。以下工作正常:
handle (\_ -> putStrLn "Error calculating result") (print x)
当我尝试删除$
编译失败时。如果我明确添加了parens,我可以让它工作,即
saferOpenFile path = handle (\_ -> return Nothing) (do
h <- openFile path ReadMode
return (Just h))
我能理解这一点,但我想我希望当Haskell点击do
时它会认为“我正在开始一个块”,我们不应该明确地放置{{1那里有分手。
我还想过将do块推到下一行,如下所示:
$
但是如果没有parens也行不通。这让我感到困惑,因为以下工作:
saferOpenFile path = handle (\_ -> return Nothing)
do
h <- openFile path ReadMode
return (Just h)
我确信我会达到接受它的程度,“这就是你如何编写惯用的Haskell”,但有没有人有任何观点可以给予?提前谢谢。
答案 0 :(得分:10)
这是由于Haskell的运营顺序。功能应用程序绑定最紧密,这可能是混乱的来源。例如:
add a b = a + b
x = add 1 add 2 3
Haskell将其解释为:函数add应用于1,函数add。这可能不是程序员的意图。 Haskell会抱怨为第二个参数预期Num,但是改为获得一个函数。
有两种解决方案:
1)表达式可以括号括起来:
x = add 1 (add 2 3)
哪个Haskell会解释为:函数add应用于1,然后是add 2 3的值。
但如果嵌套太深,这可能会让人感到困惑,因此第二种解决方案。
2)$运营商:
x = add 1 $ add 2 3
$是一个将函数应用于其参数的运算符。 Haskell将其读作:将函数(add 1)应用于add 2的值3.请记住,在Haskell中,函数可以部分应用,因此(add 1)是一个参数的完全有效函数。
$运算符可以多次使用:
x = add 1 $ add 2 $ add 3 4
您选择的解决方案将取决于您认为在特定情况下更具可读性。
答案 1 :(得分:10)
这实际上并不特定于do
- 符号。您也无法撰写print if x then "Yes" else "No"
或print let x = 1+1 in x*x
等内容。
您可以在chapter 3 of the Haskell Report开头的语法定义中验证这一点:do
表达式或条件或let
表达式是 lexp ,但是函数应用程序的参数是 aexp ,而 lexp 通常无法作为 aexp 。
当然,这并没有告诉你为什么在报告中作出选择。如果我不得不猜测,我可能会猜测它是将有用的规则“功能应用程序比其他任何东西更紧密地”扩展到例如do
和它的块之间的绑定。但我不知道这是否是最初的动机。 (我发现像print if x then ...
这样被拒绝的表单难以阅读,但这可能只是因为我习惯于阅读Haskell接受的代码。)
答案 2 :(得分:6)
根据Haskell 2010 report,do
desugars这样:
do {e;stmts}
= e >>= do {stmts}
do {p <- e; stmts} = let ok p = do {stmts}
ok _ = fail "..."
in e >>= ok
对于第二种情况,将它写成这样的desugaring是相同的(并且更容易用于演示目的):
do {p <- e; stmts}
= e >>= (\p -> do stmts)
所以假设你写这个:
-- to make these examples shorter
saferOpenFile = f
o = openFile
f p = handle (\_ -> return Nothing) do
h <- o p ReadMode
return (Just h)
令人厌恶:
f p = handle (\_ -> return Nothing) (o p ReadMode) >>= (\h -> return (Just h))
与此相同:
f p = (handle (\_ -> return Nothing) (o p ReadMode)) >>= (\h -> return (Just h))
即使你可能想要它意味着这个:
handle (\_ -> return Nothing) ((o p ReadMode) >>= (\h -> return (Just h)))
tl; dr - 当语法被删除时,它不会像您认为的那样将整个语句括起来(从Haskell 2010开始)。
这个答案只是AFAIK和
注意:如果你对我说“但实际的desugaring使用'let'语句应该分组e >>= ok
,对吧?”,那么我会回答tl; dr for do语句:它没有像你认为的那样括号/组。