В Haskell, почему мне нужно поставить $before my do block?

[Этот вопрос мотивирован главой 9 в "Real World Haskell" ]

Здесь простейшая функция (вырезанная по сути):

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, он должен подумать "Я начинаю блок", и нам не нужно явно помещать $ туда сломать вещи.

Я также думал о том, чтобы нажать блок do на следующую строку, например:

saferOpenFile path = handle (\_ -> return Nothing)  
  do
    h <- openFile path ReadMode
    return (Just h)

но это не работает без парнов. Это меня смущает, потому что следующие работы:

add a b = a + b

addSeven b = add 7
   b

Я уверен, что просто дойду до точки, где я принимаю это как "это то, как вы пишете идиоматический Haskell", но есть ли у кого есть какая-то перспектива? Спасибо заранее.

Ответ 1

Согласно отчету Haskell 2010, 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 для операторов do: t parenthesize/group, как вы думаете.

Ответ 2

Это происходит из-за порядка операций Хаскелла. Функциональное приложение связывает самые плотные, что может быть источником путаницы. Например:

add a b = a + b
x = add 1 add 2 3

Haskell интерпретирует это как: добавляет функцию к 1, а функция добавляет. Вероятно, это не то, что планировал программист. Haskell будет жаловаться на ожидание Num для второго аргумента, но вместо этого получить функцию.

Существует два решения:

1) Выражение может быть заключено в скобки:

x = add 1 (add 2 3)

Какой Haskell будет интерпретировать как: добавленная функция добавляется к 1, тогда значение add 2 3.

Но если вложенность становится слишком глубокой, это может запутать, следовательно, второе решение.

2) Оператор $:

x = add 1 $ add 2 3

$- оператор, который применяет функцию к своим аргументам. Haskell читает это как: функция (добавление 1) применяется к значению add 2 3. Помните, что в Haskell функции могут быть частично применены, поэтому (добавление 1) является вполне допустимой функцией одного аргумента.

Оператор $можно использовать несколько раз:

x = add 1 $ add 2 $ add 3 4

Какое решение, которое вы выбираете, будет определяться тем, что, по вашему мнению, более читаемо в определенном контексте.

Ответ 3

Это действительно не совсем специфично для do -notation. Вы также не можете писать такие вещи, как print if x then "Yes" else "No" или print let x = 1+1 in x*x.

Вы можете проверить это из определения грамматики в начале главы 3 отчета Haskell: выражение do или условное выражение или выражение let является lexp, но аргумент приложения функции является aexp, а lexp обычно недействителен как aexp.

Это не говорит вам, почему этот выбор был сделан в Отчете, конечно. Если бы я должен был догадаться, я мог бы предположить, что это расширяет полезное правило, которое "приложение приложения связывает более жестко, чем что-либо другое" с привязкой между, скажем, do и его блоком. Но я не знаю, была ли это оригинальная мотивация. (Я считаю, что отклоненные формы, такие как print if x then ..., трудно читать, но это может быть только потому, что я привык читать код, который принимает Haskell.)