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