В 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.)