在Haskell中,为什么我需要在我的阻止之前放一个$?

时间:2011-02-06 03:47:35

标签: haskell

[这个问题的动机来自“真实世界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”,但有没有人有任何观点可以给予?提前谢谢。

3 个答案:

答案 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 reportdo 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和

  1. 可能不正确
  2. 如果正确的话,有一天可能会过时
  3. 注意:如果你对我说“但实际的desugaring使用'let'语句应该分组e >>= ok,对吧?”,那么我会回答tl; dr for do语句:它没有像你认为的那样括号/组。