Using a Monadic eDSL from the REPL

ghci, haskell, monads, read-eval-print-loop

Solution

To an extent.

I don't believe it can be done for arbitrary Monads/instruction sets, but here's something that would work for your example. I'm using operational with an IORef to back the REPL state.

data DSLInstruction a where
    Push :: Int -> DSLInstruction ()
    Pop :: DSLInstruction Int

type DSL a = Program DSLInstruction a

push :: Int -> DSL ()
push n = singleton (Push n)

pop :: DSL Int
pop = singleton Pop

-- runDslState :: DSL a -> State [Int] a
-- runDslState = ...

runDslIO :: IORef [Int] -> DSL a -> IO a
runDslIO ref m = case view m of
    Return a -> return a
    Push n :>>= k -> do
        modifyIORef ref (n :)
        runDslIO ref (k ())
    Pop :>>= k -> do
        n <- atomicModifyIORef ref (\(n : ns) -> (ns, n))
        runDslIO ref (k n)

replSession :: [Int] -> IO (Int -> IO (), IO Int)
replSession initial = do
    ref <- newIORef initial
    let pushIO n = runDslIO ref (push n)
        popIO = runDslIO ref pop
    (pushIO, popIO)

Then you can use it like:

> (push, pop) <- replSession [] -- this shadows the DSL push/pop definitions
> push 10
> pop
10
> push 100

It should be straightforward to use this technique for State/Reader/Writer/IO-based DSLs. I don't expect it to work for everything though.

Problem

Say I have created myself an embedded domain specific language in Haskell using a monad. For example a simple language that lets you push and pop values on a stack, implemented using the state monad: ``` type DSL a = State [Int] a push :: Int -> DSL () pop :: DSL Int ``` Now I can write small stack manipulation programs using do notation: ``` program = do push 10 push 20 a <- pop push (5*a) return a ``` However, I would really like to use my DSL interactively from a REPL (GHCi in particular, willing to use other if it would help). Unfortunately having a session like: ``` >push 10 >pop 10 >push 100 ``` Does not immediately work, which is probably rather reasonable. However I really think being able to do something with a similar feel to that would be cool. The way the state monad work does not lend itself easily to this. You need to build up your `DSL a` type and then evaluate it. Is there a way to do something like this. Incrementally using a monad in the REPL? I have been looking at things like operational, MonadPrompt, and MonadCont which I sort of get the feeling maybe could be used to do something like this. Unfortunately none of the examples I have seen addresses this particular problem.

Original source