BinderAnn-0.1.0.0: test/Monadic.hs
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# OPTIONS_GHC
-fplugin BinderAnn.Monadic
-fplugin-opt BinderAnn.Monadic:manual
#-}
module Monadic where
import Prelude hiding (lookup)
import qualified Data.List as List
import Control.Monad.State
import Control.Monad.Except
import BinderAnn.Monadic
----------------------------------------
-- Example 1: Statefull arithmetic evaluator
----------------------------------------
-- the state layer
newtype Var = Var Int deriving (Show, Eq)
type Env = [(Var, Double)]
type CallStack = [SrcInfo]
data EvalState =
EvalState
{ st_uniq :: Var
, st_env :: Env
, st_stack :: CallStack
} deriving Show
emptyState :: EvalState
emptyState = EvalState (Var 0) [] []
-- the error handling layer
data EvalError =
VarNotFound CallStack Var
| DivByZero CallStack
deriving Show
----------------------------------------
-- the evaluation monad
type MonadEval m =
( MonadState EvalState m
, MonadError EvalError m
)
type Eval = StateT EvalState (ExceptT EvalError IO)
runEval :: Eval Double -> IO (Either EvalError Double)
runEval = runExceptT . flip evalStateT emptyState
getEnv :: MonadEval m => m Env
getEnv = gets st_env
getCallStack :: MonadEval m => m CallStack
getCallStack = gets st_stack
lookup :: MonadEval m => Var -> m Double
lookup i = do
e <- getEnv
case List.lookup i e of
Just n -> return n
Nothing -> do
stack <- getCallStack
throwError (VarNotFound stack i)
store :: MonadEval m => Double -> m Var
store n = state $ \(EvalState (Var u) e p) ->
(Var u, EvalState (Var (u + 1)) ((Var u, n) : e) p)
binop :: MonadEval m => (Double -> Double -> Double) -> m Var -> m Var -> m Var
binop f ex ey = do
x <- lookup =<< ex
y <- lookup =<< ey
store (f x y)
----------------------------------------
-- annotation support
instance AnnotatedM Eval a where
annotateM m info =
-- Note; using a do statement here will produce an infinite annotation loop!
-- In practice this shouldn't happen, as this instance won't appear in a
-- module using the plugin.
modify (\st -> st { st_stack = info : st_stack st }) >> m
----------------------------------------
-- the DSL
var :: MonadEval m => Var -> m Var
var = return
lit :: MonadEval m => Double -> m Var
lit = store
ret :: MonadEval m => m Var -> m Double
ret ex = lookup =<< ex
(.+) :: MonadEval m => m Var -> m Var -> m Var
(.+) = binop (+)
(.-) :: MonadEval m => m Var -> m Var -> m Var
(.-) = binop subtract
(.*) :: MonadEval m => m Var -> m Var -> m Var
(.*) = binop (*)
(./) :: MonadEval m => m Var -> m Var -> m Var
(./) ex ey = do
y <- ey
vy <- lookup y
if vy /= 0
then binop (/) ex ey
else do
stack <- getCallStack
throwError (DivByZero stack)
----------------------------------------
-- tests
{-# ANN test1 SrcInfo #-}
test1 :: Eval Double
test1 = do
x <- lit 0
y <- lit 3 .+ var x
ret (var y ./ var x)
{-# ANN test2 SrcInfo #-}
test2 :: Eval Double
test2 = do
x <- lit 3 .+ var (Var 123)
ret (var x)
-- a test for Maciej's use case
newtype Foo a = Foo a
litFoo :: Double -> Eval (Foo Var)
litFoo x = Foo <$> lit x
{-# ANN test3 SrcInfo #-}
test3 :: Eval Double
test3 = do
Foo x <- litFoo 42
ret (var x ./ lit 0)
----------------------------------------
-- entry point
----------------------------------------
tests :: IO ()
tests = do
putStrLn "test1:"
runEval test1 >>= print
putStrLn "test2:"
runEval test2 >>= print
putStrLn "test3:"
runEval test3 >>= print
return ()