02-Do-Notation.purs

module Syntax.Prelude.Notation.Do where

import Prelude

{-
In imperative programming, one often writes some sequential code like:
  x = 4
  y = x + 4
  z = toString x
  print z

Since each line depends on the line before it,
  this implies sequential computation, or Monads. We have "do" notation
  to imitate this style of code

Recall the type signature of `bind`...
      bind :: m a -> (a -> m b) -> m b
... and that ">>=" is an alias for `bind`

Thus, these two expressions are the same:
    bind computation (\result -> newComputationUsing result)
    computation >>= (\result -> newComputationUsing result)
-}

do1_bind :: Box Unit
do1_bind =
  bind get4 (\x ->
    bind (add4To x) (\y ->
      bind (toString y) (\z ->
        print z
      )
    )
  )
-- which is better understood by replacing `bind` with ">>=" as
do1_alias :: Box Unit
do1_alias =
  get4 >>= (\x ->
    -- only call `add4To x` if `get4` actually produces something
    add4To x >>= (\y ->
      toString y >>= (\z ->
        print z
      )
    )
  )
-- which is better understood and more readable as
do1_do_notation :: Box Unit
do1_do_notation = do
  x     <- get4
  y     <- add4To x
  z     <- toString y
  -- last line in do notation must not end with `value <- computation`
  -- but should just end in `computation`
  print z

-- Just like in regular functions, we could use 'let-in` syntax
do2_bind :: Box Unit
do2_bind =
  bind get4 (\x ->
      let y = x + 4
      in bind (toString y) (\z -> print z)
    )
-- which is better understood by replacing `bind` with ">>=" as
do2_alias :: Box Unit
do2_alias =
  get4 >>= (\x ->
      -- While replacing 'bind' with '>>=' is better,
      -- this let-in syntax is still not very readable here...
      let y = x + 4
      in toString y >>= (\z -> print z)
    )
-- but we can write it better as
do2_do_notation :: Box Unit
do2_do_notation = do
  x <- get4
  let y = x + 4   -- no need to have a corresponding `in` statement
  z <- toString y
  print z

do3_ignoreValue_bind :: Box Unit
do3_ignoreValue_bind =
  bind get4 (\x ->
    bind (takeValueAndIgnoreResult x) (\_ {- this underscore is 'unit' -} ->
      print x
    )
  )
-- which is better understood by replacing `bind` with ">>=" as in
do3_ignoreValue_alias :: Box Unit
do3_ignoreValue_alias =
  get4 >>= (\x -> takeValueAndIgnoreResult x >>= (\_ -> print x))
-- gets turned into...
do3_ignoreValue_do_notation :: Box Unit
do3_ignoreValue_do_notation = do
  x     <- get4
  _     <- takeValueAndIgnoreResult x

  print x

do4_discard_bind :: Box Unit
do4_discard_bind =
  bind (Box unit) (\unit_ ->
    bind (Box unit) (\unit__ ->
      print 5
    )
  )
-- which is better understood by replacing `bind` with ">>=" as in
do4_discard_alias :: Box Unit
do4_discard_alias =
  (Box unit) >>= (\unit_ ->
    (Box unit) >>= (\unit__ ->
        print 5
    )
  )
-- can be written as...
do4_discard_syntax :: Box Unit
do4_discard_syntax = do                                           {-
  When we omit the "binding <-" syntax, as in

      four <- Box 4   -- line 1
              Box a   -- line 2
      five <- Box 5   -- line 3

  the compiler translates `line 2` to
      "discard (Box a) (\_ -> (Box 5) >>= (\five -> ... ))"

  This is fine if the argument to the next function would be Unit

      four <- Box 4
      unit <- Box unit  -- here, we could omit the "unit <-" syntax
      five <- Box 5
              Box unit -- same thing

  If we had accidentally written code that amounted to this...

      four <- Box 4
              Box 10 -- notice how there is no "10 <-" fragment
      five <- Box 5

  ... the compiler would notify us that we had discarded a non-unit value
      (i.e. 10):
        "Could not find instance of Discard for Int"

  Why does it do this? To highlight that we have accidentally dropped the
  result of the computation. If you want to intentionally drop a result,
  use `void $ monad` or the "_ <- computation" syntax                         -}
  x <- (Box 5)
  (Box unit) -- since it returns unit, it's ok to use discard here

  -- rather than write this...
  map (\_ -> unit) (Box 5)

  -- or even this...
  (\_ -> unit) <$> (Box 5)

  -- we write this:
  void $ Box 5

  print 5

do_full_syntax :: Box Unit
do_full_syntax = do
  x <- get4

  _ <- takeValueAndIgnoreResult x

  (Box unit)
  void $ takeValueAndIgnoreResult x

  let y = x + 4
  z <- toString y
  -- last line in do notation must NOT end with `value <- expression`
  -- but should just end in `expression`
  print z

-- needed to make this file compile
data Box a = Box a

derive instance Functor Box

instance Apply Box where
  apply (Box f) (Box a) = Box (f a)

instance Applicative Box where
  pure a = Box a

instance Bind Box where
  bind (Box a) f = f a

get4 :: Box Int
get4 = Box 4

add4To :: Int -> Box Int
add4To i = Box (i + 4)

toString :: Int -> Box String
toString i = Box (show i)

takeValueAndIgnoreResult :: forall a. a -> Box a
takeValueAndIgnoreResult a = Box a

print :: forall a. a -> Box Unit
print _ = Box unit