Монадическая нотация do внутри let, возможно ли это?

Рассмотрим следующий допустимый код Haskell

module Main where

main :: IO ()
main = do
  let x = f
  print x

f :: Maybe (Int, Int)
f =
  Just 3 >>= (\a ->
    Just 5 >>= (\b ->
      return (a, b)))

где функция f может быть эквивалентно переписана с помощью do-нотации, подобной этой

f :: Maybe (Int, Int)
f = do
  a <- Just 3
  b <- Just 5
  return (a, b)

Что меня раздражает, нотация do не будет работать, когда я вставлю содержимое f в строку. Следующий код даже не анализируется:

main :: IO ()
main = do
  let x = do
    a <- Just 3
    b <- Just 5
    return (a, b)
  print x

Правильно ли я понимаю, что внутри let я вынужден прибегать к (>>=)?

Пока я занимаюсь этим, следующий код также не анализируется:

module Main where

main :: IO ()
main = do
  let x =
    Just 3 >>= (\a ->
      Just 5 >>= (\b ->
        return (a, b)))
  print x

Я не вижу очевидной причины, кроме ненужной ограниченной мощности let. Есть ли элегантный способ использовать bind внутри let?


person ruben.moor    schedule 27.11.2015    source источник
comment
Только не пиши in...   -  person Willem Van Onsem    schedule 27.11.2015
comment
Это явно проблема с отступами, но я недостаточно сильный мастер отступов, чтобы ответить на нее без GHC передо мной.   -  person dfeuer    schedule 27.11.2015


Ответы (1)


Правильно ли я понимаю, что внутри let я вынужден прибегать к (>>=)?

No:

main :: IO ()
main = do
  let x = do
      a <- Just 3
      b <- Just 5
      return (a, b)
  print x

Правило макета в Haskell требует, чтобы тело привязки e в p = e должно быть предназначено как минимум вплоть до начала p (или первой привязки, если вы используете сразу несколько). Поскольку let в do следует (почти) тем же правилам, что и let … in, вы можете проверить это с помощью следующей функции:

f :: Int
f = 
  let x =
    3 + 5
  in x

Это не работает, поскольку 3 + 5 не имеет такого же или большего уровня отступа, как x. Однако,

f :: Int
f =
  let x =
       3 + 5
  in x

работает. Кроме того, хотя приведенный выше main работает, он на самом деле не означает, что a и b находятся в блоке do блока x, поэтому немного лучше сделать для них немного больший отступ:

main :: IO ()
main = do
  let x = do
        a <- Just 3
        b <- Just 5
        return (a, b)
  print x
person Zeta    schedule 27.11.2015
comment
Я предпочитаю по возможности поднимать let из do блоков. let x = ... in print x. - person dfeuer; 27.11.2015
comment
@dfeuer: это перевод по умолчанию в соответствии с 3.14. - person Zeta; 27.11.2015
comment
О, я знаю, что это делает то же самое; Я просто предпочитаю вытаскивать любые let, которые не зависят от результатов монадических действий, чтобы сделать их независимость визуально очевидной. - person dfeuer; 27.11.2015
comment
Также можно избежать необходимости выравнивать подобные вещи, просто поставив новую строку + отступ после let, как и любое другое ключевое слово макета, такое как do, of или where. - person Jon Purdy; 20.03.2021