2

I'm trying to build a UI with the VTY-UI library. I'm also using a custom monad (a few monads stacked on top of eachother). For regular IO functions, this is not a problem. I can just lift them into my monad. However, the VTY-UI function onActivate has this type signature:

onActivate :: Widget Edit -> (Widget Edit -> IO ()) -> IO ()

Is there a way to turn a Widget Edit -> MyMonad () function into a (Widget Edit -> IO ()) without having to wrap and unwrap my monad? I'd rather not rewrite all the library's type signatures to be MonadIO m => m () instead of IO ().

Syd Kerckhove
  • 783
  • 5
  • 19

3 Answers3

3

The function liftBaseOpDiscard from monad-control seems to do the trick:

import Control.Monad.Trans.Control 

type MyMonad a = ReaderT Int (StateT Int IO) a

onActivate' :: Widget Edit -> (Widget Edit -> MyMonad ()) -> MyMonad ()
onActivate' = liftBaseOpDiscard . onActivate 

This function has a MonadBaseControl constraint, but ReaderT and StateT on top IO already have instances for that typeclass.

As the documentation for liftBaseOpDiscard mentions, changes to the state inside the callback will be discarded.

MonadBaseControl lets you temporarily hide the upper layers of a monad stack into a value of the base monad of the stack (liftBaseWith) and afterwards pop them again, if needed (restoreM).

Edit: If we need to preserve effects that take place inside the callback (like changes in the state) one solution is to "mimic" state by using an IORef as the environment of a ReaderT. Values written into the IORef are not discarded. The monad-unlift package is built around this idea. An example:

import Control.Monad.Trans.Unlift 
import Control.Monad.Trans.RWS.Ref
import Data.IORef

-- use IORefs for the environment and the state
type MyMonad a = RWSRefT IORef IORef Int () Int IO a

onActivate' :: Widget Edit -> (Widget Edit -> MyMonad ()) -> MyMonad ()
onActivate' we f = do 
    -- the run function will unlift into IO
    UnliftBase run <- askUnliftBase
    -- There's no need to manually "restore" the stack using
    -- restoreM, because the changes go through the IORefs
    liftBase $ onActivate we (run . f)

The monad can be run afterwards using runRWSIORefT.

danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • I'm guessing there is no way to keep the sate changes? I'm supposed to pass 'things that happen when I press a key' to this functon. I didn't know about this functionality though. Thank you! – Syd Kerckhove May 03 '15 at 15:19
  • @Syd Kerckhove I don't know how to do it. The problem is that the callback returns `IO ()`, so there's no way to encode the "extra stuff" needed to restore the hidden monad stack layers afterwards. Perhaps you could use a mutable reference as the state, like `Control.Monad.Trans.State.Ref` from package `monad-unlift` does. Plus, the transformers in that package are easy to "unlift" into `IO`. https://www.fpcomplete.com/blog/2015/04/announcing-monad-unlift – danidiaz May 03 '15 at 15:30
  • This looks promising. Could you write me an example? Or should I look into rewriting the vty-ui library to use MonadIO instead of IO? – Syd Kerckhove May 03 '15 at 16:12
  • @SydKerckhove [You probably will not succeed at lifting everything in `vty-ui` into `MonadIO`.](http://stackoverflow.com/q/9243215/791604) But [you are not the first to have to deal with state and GUI libraries](http://stackoverflow.com/q/12002814/791604). – Daniel Wagner May 03 '15 at 20:02
  • 1
    @Syd Kerckhove After some reconsideration, perhaps the best solution would be to use channels: make the callback write events into a `Chan` or `TChan`, and have another thread that reads events from the channel and processes them. – danidiaz May 05 '15 at 19:29
0

For the state part: you can use this module. Thanks to whoever realized that making get and put polymorphic was a good idea.

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
module IState where
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Trans.Class
import Control.Applicative
import Data.IORef

newtype IState s m a = IState (ReaderT (IORef s) m a)

runIState (IState a) s = do
  sr <- liftIO $ newIORef s
  runReaderT a sr

runIStateRef (IState a) r = runReaderT a r

instance (Monad m) => Monad (IState s m) where
  return = IState . return
  (IState a) >>= f = let
    f' i = let (IState i') = f i in i'
    in IState (a >>= f')

instance (Monad m,Functor m) => Applicative (IState s m) where
  pure = return
  (<*>) = ap

instance (Functor m) => Functor (IState s m) where
  fmap f (IState a) = IState (fmap f a)

instance (MonadIO m) => MonadIO (IState s m) where
  liftIO = lift . liftIO

instance (MonadState s' m) => MonadState s' (IState s m)   where
  get = lift get
  put = lift . put

-- Because of this instance IState is almost a drop-in replacement for StateT
instance (MonadIO m) => MonadState s (IState s m) where
  get = IState $ do
    r <- ask
    liftIO $ readIORef r
  put v = IState $ do
    r <- ask
    liftIO $ writeIORef r v

instance MonadTrans (IState s) where
  lift a = IState (lift a)
Jeremy List
  • 1,756
  • 9
  • 16
0

I managed to implement the suggestion mentioned in the comments of the question.

I give vty callbacks in IO that sends events down a Chan. Then i have another thread listening for those events and executing the appropriate actions in my own monad.

Syd Kerckhove
  • 783
  • 5
  • 19