2

Suppose I want to create a wrapping Monad that takes an IO Action and wraps it as follows. The sequence operator (>>) works just fine, but I'm having a hard time implementing return and >>=.

I tried return x = DelayedAction (return x) but that doesn't result in the correct type.

newtype DelayedAction a = DelayedAction {action :: IO a}

instance Functor DelayedAction where
  fmap = liftM

instance Applicative DelayedAction where
  pure = return
  (<*>) = ap

instance Monad DelayedAction where
  return x = undefined
  (DelayedAction firstIO) >>= f = undefined

  (DelayedAction firstIO) >> (DelayedAction secondIO) =
    DelayedAction
      ( do
          firstIO
          threadDelay 1000000
          secondIO
      )
AgentLiquid
  • 3,632
  • 7
  • 26
  • 30
  • 6
    This can't follow the monad laws. If you add a delay every bind, `m >>= return` will not equal `m`. See also: [Is it possible to create a Monad that count the number of instructions?](https://stackoverflow.com/q/7956668/5923139) and [Seeking constructive criticism on monad implementation](https://stackoverflow.com/q/4765260/5923139). This isn't to say that it's *impossible* to write an implementation, it just means that you *shouldn't*. – Aplet123 Dec 16 '20 at 01:02
  • 1
    Right. However, this _could_ be fixed in this case by putting the delays in every action _except `return`_. The most practical way to achieve this would be to just copy the monad instance wholesale, but not export the `DelayedAction` constructor but only `delayedAction a = threadDelay 1000000 >> a`. – leftaroundabout Dec 16 '20 at 03:08
  • @leftaroundabout - this is interesting, can you expand? What do you mean by "copy the monad instance wholesale"? – AgentLiquid Dec 16 '20 at 05:26

1 Answers1

1

@Aplet123 and @leftaroundabout clarified that this can't be a real monad since it can't follow Monad laws. Nonetheless, I was able to come up with a solution that made the compiler happy. Taught me a lot about monads, type-classes, types, do-notation, etc.

instance Monad DelayedAction where
  return x = DelayedAction (return x)

  da >>= f =
    DelayedAction
      ( do
          firstIOValue <- action da
          threadDelay 1000000
          (action . f) firstIOValue
      )

De-sugared version of bind:

da >>= f =
  DelayedAction
    ( action da >>= (\firstIOValue -> threadDelay 1000000 >> (action . f) firstIOValue)    
AgentLiquid
  • 3,632
  • 7
  • 26
  • 30