Questions tagged [do-notation]

In Haskell, do-notation is syntactic sugar for writing monadic code, expressing computations as sequences of monadic actions. Every participant monadic value in the do block must belong to the same monad.

In Haskell, do-notation is syntactic sugar for writing monadic code expressing computations as sequences of monadic actions, each action possibly calculated -- purely -- from the preceding action's computed results.

Every participant monadic value in the same do block must belong to the same monad.

do-notation explained, in vivid colors.

do blocks translate into monadic code, but we can view this fact as implementational detail, at first. We can treat the do notation axiomatically, as an embedded language.

do blocks express sequences of monadic "actions". A simplified, standardized do-syntax is:

   do {  pattern1 <- action1
      ;  pattern2 <- action2
      ;  pattern3 <- action3
      .....................
      ;  return result
      }

Each actioni is a Haskell expression of type M ai for some monad M and some result type ai, with M the same for all is. (Examples are: Maybe a, Maybe b, Maybe c; [a], [b], [c]; IO a, IO b, IO c; etc.)

Thus each actioni "produces" its own result type ai "in" the same monad M as all the other actions in the do block.

The overall type of a do block expression is M an where an is the type of the argument result in the final return result action (which thus has the type M an). Put differently, the type of the final action in the do block is the type of the overall do block.

In particular (and a frequent cause of confusion while learning), if actioni is an if expression, each of its branches must be an expression of the same M ai type, an expression that itself could be a do block, if it needs to sequence several M actions.

Each patterni becomes match-bound to the "computed" result from the corresponding actioni, when the whole combined "computation" described by the whole do block actually "runs" (whatever that means specifically, for the specific M type). If the pattern is refutable, and the pattern match fails, the do computation chain is aborted through the invocation of M's fail method; otherwise, the computation chain continues to the next do line.

Each patterni's variable is in scope from the point of its introduction and to the end of the do block. In particular this means that the result expression in the final return result action may refer to any of the patterni's variables, as may any actionj where j > i.

It also means that the binding construct <- is non-recursive: the scope to its right (and above) contains the scope to its left (and below). That in turn means that if any variable is bound more than once, the deeper (nested) binding shadows the outer binding -- any use of a variable's name refers to its most recent binding only.

Wildcards _ can be used to ignore the computed value. If this is the case, the _ <- part can be omitted altogether.

Another special case is that by Monad laws, do { .... ; x <- action ; return x } is equivalent to do { .... ; action }.

The translation of a do code into actual monadic code goes by the rule

   do { pat <- act ; ... }
===
   act >>= f  where
           f pat = do { ... }
           f _   = fail "error-message"

and

   do { act }
===
   act

Optionally, let can also appear in a do block, which has a simple syntax re-write:

   do { actions... 
      ; let {...}          -- no "in", NB!
      ; more_actions...
      }
===
   do { actions...
      ; let {...} 
        in
          do { more_actions... }
      }
159 questions
4
votes
2 answers

Why does the type match on the next line but not on the same line in `do` block?

I’m reading in several lines from the input that the user has to type: main :: IO () main = do let size = 3 arr <- replicateM size getLine let pairs = map parsePair arr print pairs Why am I allowed to do map parsePair arr on a separate…
Albizia
  • 517
  • 8
  • 18
4
votes
2 answers

How does the Haskell `do` notation know which value to take when it isn't defined by a return?

I have this monadic object. data Parser a = Parser (String -> Maybe (a, String)) instance Functor Parser where -- fmap :: (a -> b) -> Parser a -> Parser b fmap f (Parser pa) = Parser $ \input -> case pa input of …
Addem
  • 3,635
  • 3
  • 35
  • 58
4
votes
1 answer

How to use IO Double as regular Double in Haskell

I have to following code isInCircle::Double->Double->Bool isInCircle p1 p2 = sqrt((p1*p1)+(p2*p2)) <= 1 and when I make a call like isInCircle (random :: Double) (random :: Double) I get this error * Couldn't match expected type `Double' with…
EvilGenius
  • 61
  • 3
4
votes
1 answer

"Do" notation in Elixir and Haskell

I have been using "do/end" notation in elixir more-or-less like imperative block delimiters. (In other words, do is like { in a C-like language, end is like }). Is this an accurate description of what's going on? Or is it more like the Haskell…
Mark Karavan
  • 2,654
  • 1
  • 18
  • 38
4
votes
2 answers

Is this syntax as expressive as the do-notation?

The do notation allows us to express monadic code without overwhelming nestings, so that main = getLine >>= \ a -> getLine >>= \ b -> putStrLn (a ++ b) can be expressed as main = do a <- getLine b <- getLine putStrLn (a ++…
MaiaVictor
  • 51,090
  • 44
  • 144
  • 286
4
votes
2 answers

ApplicativeDo not working with sequencing

I have this type, basically a Kleisli arrow: {-# language DeriveFunctor #-} data Plan m i o = Plan (i -> m o) deriving Functor instance (Monad m) => Applicative (Plan m i) where pure x = Plan (\_ -> pure x) Plan f <*> Plan x = Plan (\i ->…
danidiaz
  • 26,936
  • 4
  • 45
  • 95
4
votes
1 answer

What is wrong with this Haskell let-in indentation?

In the below Haskell code, I get an error "parse error on input 'in'". From what I've read, the indentation I've used should be fine. I in fact use 'let' and 'in' with similar indentation ('let' aligned with 'in') successfully elsewhere in my code.…
user1920703
3
votes
1 answer

Haskell - syntax in do blocks (using IO)

The compiler says The last statement in a 'do' construct must be an expression: rmax <- getInteger when attempting to load a file containing the following snippets of code: getInteger :: IO Integer getInteger = readLn main :: IO () main = do …
user1125052
3
votes
1 answer

How does this trick type-check?

Reading this blog post – https://www.haskellforall.com/2021/05/the-trick-to-avoid-deeply-nested-error.html – I realised I don't understand why the 'trick' actually works in this situation: {-# LANGUAGE NamedFieldPuns #-} import Text.Read…
Iarek
  • 1,220
  • 15
  • 40
3
votes
2 answers

Need explanation for basic do block syntax

In ghci, I wrote: let x = do i <- [1..5] j <- [2..4] return i Expected result: [1,2,3,4,5] Actual result: [1,1,1,2,2,2,3,3,3,4,4,4,5,5,5] I don't understand the logic behind that output. I think the reason might be something about…
ICFSZ
  • 51
  • 4
3
votes
2 answers

How to use Do notation with both Maybe and IO

I am trying to get a good grip on the do notation in Haskell. I could use it with Maybe and then print the result. Like this: maybeAdd :: Maybe Integer maybeAdd = do one <- maybe1 two <- maybe2 three <- maybe3 …
Finlay Weber
  • 2,989
  • 3
  • 17
  • 37
3
votes
2 answers

Haskell do notation edge case fails to typecheck

I'm trying to understand the rules for do notation. Here is some code that typechecks: fff :: Maybe Int fff = do _ <- pure $ Just 100 (+10) <$> Just 50 Which is basically fff = (+10) <$> Just 50. I would assume the above could would not…
Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
3
votes
3 answers

Understanding do notation for simple Reader monad: a <- (*2), b <- (+10), return (a+b)

instance Monad ((->) r) where return x = \_ -> x h >>= f = \w -> f (h w) w import Control.Monad.Instances addStuff :: Int -> Int addStuff = do a <- (*2) b <- (+10) return (a+b) I'm trying to understand this…
Guerlando OCs
  • 1,886
  • 9
  • 61
  • 150
3
votes
3 answers

Why can't I put a print function statement here?

I am trying following code with try-catch block: import System.Environment import System.IO import System.IO.Error import Control.Exception isBinary :: String -> Bool isBinary ss = do print "In isBinary fn" -- works if this line is…
rnso
  • 23,686
  • 25
  • 112
  • 234
3
votes
3 answers

How to wrap char literal in IO monad in Haskell?

I know you're supposed to wrap up the operations you want to perform on a result in a monad rather than unwrap things from the monad. What I can't find are any idiot-friendly examples of how to do that. For example, I want to do something like…
James Strieter
  • 775
  • 3
  • 10