Using Monadic eDSL from REPL

Let's say I created a built-in domain language in Haskell using the monad. For example, a simple language that allows you to push and set 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 file manipulation programs using the 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 REPL (in particular, GHCi, wanting to use others if that helps).

Unfortunately, a session like:

>push 10
>pop
10
>push 100

Doesn't work right away, which is probably pretty reasonable. However, I really think I can do something with a similar feeling to make it cool. The way the state monad works is easily amenable to this. You need to create your type DSL aand then evaluate it.

- . REPL?

, operational, MonadPrompt MonadCont, , , , - . , , , .

+4
2

.

, Monads/, -, . operational IORef REPL.

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)

:

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

DSL, State/Reader/Writer/IO. , .

+3

- , - . . :

{-# LANGUAGE RankNTypes #-}

import Data.IORef
import Data.Proxy

newtype REPL m f = REPL { run :: forall a. m a -> IO (f a) }

newREPL :: (Monad m) => Proxy m -> (forall a. m a -> f a) -> IO (REPL m f)
newREPL _ runM = do
    accum <- newIORef (return ())
    return $ REPL (\nextAction -> do
        actions <- readIORef accum
        writeIORef accum (actions >> nextAction >> return ())
        return (runM (actions >> nextAction)))

, , IORef, , -, .

, newREPL, Proxy "", . , m a -> f a m a -> a, , - , , f like:

data StateOutput a = StateOutput a [Int]
    deriving (Show)

Identity, .

Proxy , ghci defaulting , repl.

:

>>> repl <- newREPL (Proxy :: Proxy DSL) (\m -> Identity (evalState m []))
>>> run repl $ push 1
Identity ()
>>> run repl $ push 2
Identity ()
>>> run repl $ pop
Identity 2
>>> run repl $ pop
Identity 1

Identity , :

newtype LineOutput a = LineOutput a
instance (Show a) => Show (LineOutput a) where
    show (LineOutput x) = show x

, -

type DSL a = State [Int] a

type DSL = State [Int]

, , , Proxy :: DSL. , , .

+4

Source: https://habr.com/ru/post/1676402/


All Articles