1

I want to create a generalized form of IO based on ContT. I created a GADT to represent different IO actions:

data Cmd a where
  PutChar :: Char -> Cmd ()
  GetChar :: Cmd Char

I wrote a function that transforms these into IO commands for use in the IO monad like so:

continueIO :: Cmd a -> IO a
continueIO (PutChar c) = putChar c
continueIO GetChar = getChar

Here's what a sample usage of this should look like:

echoChar :: (Monad m) => ContT r m (Cmd a)
echoChar = ContT $ \k -> do
  c <- k GetChar
  k (PutChar c)

It should be run with something like runContT echoChar continueIO. However, the PutChar and GetChar commands conflict in their types. How can I dispatch both types from the same ContT?

P.S. Sorry if anything in this question is awkwardly worded. I'm trying to give myself a challenge here and I don't completely understand what I'm trying to do.

Edit: I am not restricted in my solution, and I do not have to use ContT.

robbie
  • 1,219
  • 1
  • 11
  • 25
  • Are you restricted to using `ContT` here? This is exactly the sort of situation `Free` or `Operational` was built to handle. – Carl Jan 05 '18 at 03:08
  • @Carl I'm not restricted at all, and I'm definitely open to exploring those solutions. Thanks! – robbie Jan 05 '18 at 03:24
  • @Carl, I think `FT Cmd m` is more likely than `Free Cmd`. – dfeuer Jan 05 '18 at 08:34
  • You might need an existential wrapper. It's hard to understand if you actually need that, though. – chi Jan 05 '18 at 09:21

1 Answers1

0

Either your commands all need to be closures that return the same type (such as IO (), in which case you can’t do getChar) or you need to have a polymorphic result type that can hold either IO () or IO Char, and ContinueIO needs to take this as its argument. Then you need to decide what happens when you pass an IO Char to GetChar or nothing to PutChar. (This version could be a Monoid with <> = ContinueIO and mempty = id, and then you could foldMap over any Foldable structure.)

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • With a polymorphic result type, how will the continuation know what to return? – robbie Jan 05 '18 at 03:04
  • Has to be defined for each case. `GetChar` would return a `Char` wrapped in an `IO Result` or whatever you call it, and `PutChar` would return `()`. They’d call different constructors. – Davislor Jan 05 '18 at 03:07
  • You could also do a more complicated AST, where a `GetChar` node has a daughter node that the type system will only let you fill with something that takes an `IO Char` as input, and if you want to ignore the result, you put in an ignore-a-`Char` command as the terminal node. – Davislor Jan 05 '18 at 03:11