prob-fx-0.1.0.2: src/Env.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
{- | This implements the model environments that users must provide upon running a model;
such environments assign traces of values to the "observable variables" (random
variables which can be conditioned against) of a model.
-}
module Env
( -- * Observable variable
ObsVar(..)
, varToStr
-- * Model environment
, Assign(..)
, Env(..)
, (<:>)
, nil
, Observable(..)
, Observables(..)
, UniqueKey
, LookupType) where
import Data.Kind ( Constraint )
import Data.Proxy ( Proxy(Proxy) )
import FindElem ( FindElem(..), Idx(..) )
import GHC.OverloadedLabels ( IsLabel(..) )
import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
import Unsafe.Coerce ( unsafeCoerce )
-- | Containers for observable variables
data ObsVar (x :: Symbol) where
ObsVar :: KnownSymbol x => ObsVar x
-- | Allows the syntax @#x@ to be automatically lifted to the type @ObsVar "x"@.
instance (KnownSymbol x, x ~ x') => IsLabel x (ObsVar x') where
fromLabel = ObsVar
-- | Convert an observable variable from a type-level string to a value-level string
varToStr :: forall x. ObsVar x -> String
varToStr ObsVar = symbolVal (Proxy @x)
-- * Model Environments
{- | A model environment assigning traces (lists) of observed values to observable
variables i.e. the type @Env ((x := a) : env)@ indicates @x@ is assigned a value
of type @[a]@.
-}
data Env (env :: [Assign Symbol *]) where
ENil :: Env '[]
ECons :: [a] -> Env env -> Env (x := a : env)
-- | Assign or associate a variable @x@ with a value of type @a@
data Assign x a = x := a
-- | Empty model environment
nil :: Env '[]
nil = ENil
infixr 5 <:>
-- | Prepend a variable assignment to a model environment
(<:>) :: UniqueKey x env ~ True => Assign (ObsVar x) [a] -> Env env -> Env ((x ':= a) ': env)
(_ := as) <:> env = ECons as env
instance (KnownSymbol x, Show a, Show (Env env)) => Show (Env ((x := a) ': env)) where
show (ECons a env) = varToStr (ObsVar @x) ++ ":=" ++ show a ++ ", " ++ show env
instance Show (Env '[]) where
show ENil = "[]"
instance FindElem x ((x := a) : env) where
findElem = Idx 0
instance {-# OVERLAPPABLE #-} FindElem x env => FindElem x ((x' := a) : env) where
findElem = Idx $ 1 + unIdx (findElem :: Idx x env)
-- | Retrieve the type of an observable variable @x@ from an environment @env@
type family LookupType x env where
LookupType x ((x := a) : env) = a
LookupType x ((x' := a) : env) = LookupType x env
-- | Specifies that an environment @Env env@ has an observable variable @x@ whose observed values are of type @a@
class (FindElem x env, LookupType x env ~ a)
=> Observable env x a where
get :: ObsVar x -> Env env -> [a]
set :: ObsVar x -> [a] -> Env env -> Env env
instance (FindElem x env, LookupType x env ~ a)
=> Observable env x a where
get _ env =
let idx = unIdx $ findElem @x @env
f :: Int -> Env env' -> [a]
f n (ECons a env) = if n == 0
then unsafeCoerce a
else f (n - 1) env
in f idx env
set _ a' env =
let idx = unIdx $ findElem @x @env
f :: Int -> Env env' -> Env env'
f n (ECons a env) = if n == 0
then ECons (unsafeCoerce a') env
else ECons a (f (n - 1) env)
in f idx env
-- | For each observable variable @x@ in @xs@, construct the constraint @Observable env x a@
type family Observables env (ks :: [Symbol]) a :: Constraint where
Observables env (x ': xs) a = (Observable env x a, Observables env xs a)
Observables env '[] a = ()
-- | Check whether an observable variable @x@ is unique in model environment @env@
type family UniqueKey x env where
UniqueKey x ((x ':= a) : env) = False
UniqueKey x ((x' ':= a) : env) = UniqueKey x env
UniqueKey x '[] = True