packages feed

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 ()