packages feed

operational-alacarte-0.3.1: examples/Advanced.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeOperators #-}

{-# OPTIONS_GHC -fno-warn-missing-methods #-}

module Advanced where

import Data.IORef

import Control.Monad.Operational.Higher



-- This file is a version of `Simple.hs` where we want to expose the fact that
-- programs have expressions as sub-structures. To do this, we use instructions
-- with types of the form
--
--     instr (Param2 p e) a
--
-- where `p` represents sub-programs and `e` represents expressions.
--
-- The `Program` type is now specialized to `Program ... '[Exp]`.
--
-- By exposing expressions in this way, we decouple the interpretation of
-- instructions from that of expressions. This can be seen in the example below
-- which defines
--
--     main = fmap eval $ interpretBi (return . eval) prog
--
-- The interpretation of instructions is obtained from the `InterpBi` class,
-- while the interpretation of expressions is provided separately.



--------------------------------------------------------------------------------
-- Simple expression language
--------------------------------------------------------------------------------

data Exp a
  where
    Lit :: a -> Exp a
    Add :: Num a => Exp a -> Exp a -> Exp a
    Eq  :: Eq a => Exp a -> Exp a -> Exp Bool

instance Num a => Num (Exp a)
  where
    fromInteger = Lit . fromInteger
    (+) = Add

eval :: Exp a -> a
eval (Lit i)   = i
eval (Add a b) = eval a + eval b
eval (Eq a b)  = eval a == eval b



--------------------------------------------------------------------------------
-- Composable instructions
--------------------------------------------------------------------------------

data Val a = Val a

-- | If statement
data If fs a
  where
    If :: e Bool -> p a -> p a -> If (Param2 p e) a

-- | Loop
data Loop fs a
  where
    Loop :: e Int -> p () -> Loop (Param2 p e) ()

-- | Mutable references
data Ref fs a
  where
    NewRef :: e a -> Ref (Param2 p e) (IORef a)
    GetRef :: IORef a -> Ref (Param2 p e) (Val a)
    SetRef :: IORef a -> e a -> Ref (Param2 p e) ()
  -- Note: `GetRef` cannot return `exp a` as that would prevent writing the
  -- `HBifunctor` instance.

instance HFunctor If
  where
    hfmap f (If c thn els) = If c (f thn) (f els)

instance HFunctor Loop
  where
    hfmap f (Loop n body) = Loop n (f body)

instance HFunctor Ref
  where
    hfmap f (NewRef a)   = NewRef a
    hfmap f (GetRef r)   = GetRef r
    hfmap f (SetRef r a) = SetRef r a

instance HBifunctor If
  where
    hbimap f g (If c thn els) = If (g c) (f thn) (f els)

instance HBifunctor Loop
  where
    hbimap f g (Loop n body) = Loop (g n) (f body)

instance HBifunctor Ref
  where
    hbimap _ g (NewRef a)   = NewRef (g a)
    hbimap _ g (GetRef r)   = GetRef r
    hbimap _ g (SetRef r a) = SetRef r (g a)

instance InterpBi If IO fs
  where
    interpBi (If c thn els) = c >>= \c' -> if c' then thn else els

instance InterpBi Loop IO fs
  where
    interpBi (Loop n body) = n >>= \n' -> replicateM_ n' body

instance InterpBi Ref IO fs
  where
    interpBi (NewRef a)   = newIORef =<< a
    interpBi (GetRef r)   = fmap Val $ readIORef r
    interpBi (SetRef r a) = writeIORef r =<< a



--------------------------------------------------------------------------------
-- Example
--------------------------------------------------------------------------------

type MyProgram a = Program (If :+: Loop :+: Ref) (Param1 Exp) a

iff :: Exp Bool -> MyProgram a -> MyProgram a -> MyProgram a
iff c thn els = singleInj $ If c thn els

loop :: Exp Int -> MyProgram () -> MyProgram ()
loop n = singleInj . Loop n

newRef :: Exp a -> MyProgram (IORef a)
newRef = singleInj . NewRef

getRef :: IORef a -> MyProgram (Exp a)
getRef r = do
    Val a <- singleInj $ GetRef r
    return $ Lit a

setRef :: IORef a -> Exp a -> MyProgram ()
setRef r = singleInj . SetRef r

prog :: MyProgram (Exp Int)
prog = do
    r <- newRef 0
    loop 10 $ do
        a <- getRef r
        iff (Eq a 3)
            (setRef r 100)
            (setRef r (a+1))
    getRef r

main = fmap eval $ interpretBi (return . eval) prog