{-# LANGUAGE DeriveGeneric, TypeOperators, DefaultSignatures, FlexibleContexts, TemplateHaskell #-} -- | This module implements variables which may contain values from -- types different from the native types (@Proc_*@ types). -- -- To make a type available to custom variables, it needs to be -- instantiated in the 'CustomValue' class, which is subclass -- of the 'VarLength' class. These instances are derivables using -- the @DeriveGeneric@ extension. Things you need are: enable the -- @DeriveGeneric@ language extension, import "GHC.Generics", derive -- a 'Generic' instance of your type and then write the following -- instances (where @Foo@ is any type of interest): -- -- > instance VarLength Foo -- > instance CustomValue Foo -- -- Note that @Foo@ must be made from other types that are instances -- of 'CustomValue'. Also, note that instances of 'VarLength' or -- 'CustomValue' can /not/ be recursive or sum types. -- An example: -- -- > {-# LANGUAGE DeriveGeneric #-} -- > -- > import Graphics.Web.Processing.Mid -- > import Graphics.Web.Processing.Mid.CustomVar -- > import GHC.Generics -- > -- > data Point = Point Proc_Float Proc_Float -- > deriving Generic -- > -- > instance VarLength Point -- > instance CustomValue Point -- -- Types instance of the 'CustomValue' class can be contained by -- a special type of variables, called 'CustomVar' (Custom Variable). -- Functions for custom variables are equal to the function for regular -- variables, except that they all end in @C@. For example, 'newVar' is -- called 'newVarC' for custom variables. -- -- There are also arrays which may contain custom values. -- See 'CustomArrayVar'. -- -- The dependency of this module in several language extensions was -- the reason to make it separate from the rest of the /mid/ interface -- where it belongs to. Somehow, it forces the user to use @DeriveGeneric@ -- and import "GHC.Generics" to do something useful with it (more than use -- custom variables for tuples). module Graphics.Web.Processing.Mid.CustomVar ( CustomVar , CustomArrayVar , customArraySize , VarLength (..) , CustomValue (..) , readArrayVarC , writeArrayVarC ) where import Data.Text (Text) import Data.Text.Lazy (toStrict) import Text.PrettyPrint.Mainland (ppr, prettyLazyText) import Data.Monoid ((<>)) import Graphics.Web.Processing.Mid import Graphics.Web.Processing.Core.Primal (varFromText,Proc_Int (..)) import Control.Monad (liftM) -- generics import GHC.Generics import Graphics.Web.Processing.Core.TH {- This module is somehow magic. It allows you handle set of variables and arrays like if they were a single variable. It is very convenient to handle other values rather than the primitive Proc_* types. The interesting part is that you can make, with some restrictions, your own type be stored in one of these variables. -} -- | Variable with custom values. data CustomVar a = CustomVar [Text] deriving Generic -- | Modify all the variable names inside a custom variable. mapCustomVar :: (Text -> Text) -> CustomVar a -> CustomVar a mapCustomVar f (CustomVar xs) = CustomVar (fmap f xs) -- | Typeclass of custom values, which can be stored in custom variables ('CustomVar'). class VarLength a => CustomValue a where -- | Version of 'newVar' for custom variables. newVarC :: (Monad (m Preamble), ProcMonad m) => a -> m Preamble (CustomVar a) default newVarC :: (Monad (m Preamble), ProcMonad m, Generic a, GCustomValue (Rep a)) => a -> m Preamble (CustomVar a) newVarC = liftM castCVar . gnewVarC . from -- | Version of 'newArrayVar' for custom variables. newArrayVarC :: (Monad (m Preamble), ProcMonad m) => [a] -> m Preamble (CustomArrayVar a) default newArrayVarC :: (Monad (m Preamble), ProcMonad m, Generic a, GCustomValue (Rep a)) => [a] -> m Preamble (CustomArrayVar a) newArrayVarC = liftM castCAVar . gnewArrayVarC . fmap from -- | Version of 'readVar' for custom variables. readVarC :: (Monad (m c), ProcMonad m) => CustomVar a -> m c a default readVarC :: (Monad (m c), ProcMonad m, Generic a, GCustomValue (Rep a)) => CustomVar a -> m c a readVarC v = liftM to $ greadVarC (castCVar v) -- | Version of 'writeVar' for custom variables. writeVarC :: (Monad (m c), ProcMonad m) => CustomVar a -> a -> m c () default writeVarC :: (Monad (m c), ProcMonad m, Generic a, GCustomValue (Rep a)) => CustomVar a -> a -> m c () writeVarC v x = gwriteVarC (castCVar v) (from x) -- | Version of 'if_' for custom values. ifC :: Proc_Bool -> a -> a -> a default ifC :: (Generic a, GCustomValue (Rep a)) => Proc_Bool -> a -> a -> a ifC b x y = to $ gifC b (from x) (from y) -- Maybe this function can be written in terms of arrayVarToVar? arrayVarToVarC :: CustomArrayVar a -> Proc_Int -> CustomVar a arrayVarToVarC v n = mapCustomVar f $ customInnerVar v where f t = t <> "[" <> (toStrict $ prettyLazyText 80 $ ppr n) <> "]" -- | Read a component of a custom array variable. readArrayVarC :: (ProcMonad m, Monad (m c), CustomValue a) => CustomArrayVar a -> Proc_Int -> m c a readArrayVarC v n = case n of Proc_Int i -> let s = customArraySize v in if (i < 0) || (i >= s) then fail $ "readArrayVarC: index out of bounds.\nArray size: " ++ show s ++ ".\nIndex given: " ++ show i ++ ".\nRemember that indices start from 0." else readVarC $ arrayVarToVarC v n _ -> readVarC $ arrayVarToVarC v n -- | Write a component of a custom array variable. writeArrayVarC :: (ProcMonad m, Monad (m c), CustomValue a) => CustomArrayVar a -> Proc_Int -> a -> m c () writeArrayVarC v n x = writeVarC (arrayVarToVarC v n) x fromVar :: Var a -> CustomVar a fromVar = CustomVar . (:[]) . varName fromCustomVar :: CustomVar a -> [Var a] fromCustomVar (CustomVar xs) = fmap varFromText xs -- Custom arrays -- | Array variable of custom values. data CustomArrayVar a = CustomArrayVar { -- | Size of the custom array. customArraySize :: Int , customInnerVar :: CustomVar a } fromArrayVar :: ArrayVar a -> CustomArrayVar a fromArrayVar v = CustomArrayVar (arraySize v) $ fromVar $ varFromText $ arrayVarName v -- | Typeclass of values that can be stored in several -- native variables ('Var'). class VarLength a where -- | Calculate how many native variables are needed -- to store a value. varLength :: a -> Int default varLength :: (Generic a, GVarLength (Rep a)) => a -> Int varLength = gvarLength . from -- GENERICS class GVarLength f where gvarLength :: f a -> Int instance GVarLength U1 where gvarLength _ = 1 instance (GVarLength a, GVarLength b) => GVarLength (a :*: b) where gvarLength (a :*: b) = gvarLength a + gvarLength b instance GVarLength (a :+: b) where gvarLength _ = error "gvarLength: Custom variables cannot contain sum types." instance GVarLength a => GVarLength (M1 i c a) where gvarLength (M1 x) = gvarLength x instance VarLength a => GVarLength (K1 i a) where gvarLength (K1 x) = varLength x varDrop :: Int -> CustomVar a -> CustomVar a varDrop n (CustomVar xs) = CustomVar $ drop n xs castCVar :: CustomVar a -> CustomVar b castCVar (CustomVar xs) = CustomVar xs castCAVar :: CustomArrayVar a -> CustomArrayVar b castCAVar (CustomArrayVar n v) = CustomArrayVar n $ castCVar v class GCustomValue f where gnewVarC :: (Monad (m Preamble), ProcMonad m) => f a -> m Preamble (CustomVar (f a)) gnewArrayVarC :: (Monad (m Preamble), ProcMonad m) => [f a] -> m Preamble (CustomArrayVar (f a)) greadVarC :: (Monad (m c), ProcMonad m) => CustomVar (f a) -> m c (f a) gwriteVarC :: (Monad (m c), ProcMonad m) => CustomVar (f a) -> f a -> m c () gifC :: Proc_Bool -> f a -> f a -> f a leftP :: (a :*: b) c -> a c leftP (a :*: _) = a rightP :: (a :*: b) c -> b c rightP (_ :*: b) = b instance (GVarLength a, GCustomValue a, GCustomValue b) => GCustomValue (a :*: b) where gnewVarC (a :*: b) = do CustomVar xs <- gnewVarC a CustomVar ys <- gnewVarC b return $ CustomVar $ xs ++ ys gnewArrayVarC l = do let as = fmap leftP l bs = fmap rightP l CustomArrayVar n (CustomVar xs) <- gnewArrayVarC as CustomArrayVar _ (CustomVar ys) <- gnewArrayVarC bs return $ CustomArrayVar n $ CustomVar $ xs ++ ys greadVarC v = do a <- greadVarC $ castCVar v let n = gvarLength a b <- greadVarC $ varDrop n $ castCVar v return $ a :*: b gwriteVarC (CustomVar v) (a :*: b) = do let (xs,ys) = splitAt (gvarLength a) v gwriteVarC (CustomVar xs) a gwriteVarC (CustomVar ys) b gifC c (a :*: b) (x :*: y) = gifC c a x :*: gifC c b y instance GCustomValue (a :+: b) where gnewVarC = error "gnewVarC: Custom variables cannot contain sum types." gnewArrayVarC = error "gnewArrayVarC: Custom variables cannot contain sum types." greadVarC = error "greadVarC: Custom variables cannot contain sum types." gwriteVarC = error "gwriteVarC: Custom variables cannot contain sum types." gifC = error "gifC: Custom values are not sum types." instance GCustomValue a => GCustomValue (M1 i c a) where gnewVarC (M1 x) = liftM castCVar $ gnewVarC x gnewArrayVarC = liftM castCAVar . gnewArrayVarC . fmap unM1 greadVarC v = liftM M1 $ greadVarC $ castCVar v gwriteVarC v (M1 x) = gwriteVarC (castCVar v) x gifC b (M1 x) (M1 y) = M1 $ gifC b x y instance CustomValue a => GCustomValue (K1 i a) where gnewVarC (K1 x) = liftM castCVar $ newVarC x gnewArrayVarC = liftM castCAVar . newArrayVarC . fmap unK1 greadVarC v = liftM K1 $ readVarC $ castCVar v gwriteVarC v (K1 x) = writeVarC (castCVar v) x gifC b (K1 x) (K1 y) = K1 $ ifC b x y {- Proc_* types as custom values Any Proc_* type can be seen as a custom value, making a trivial instance to the CustomValue class, using custom variables as usual variables. For any Proc_* type: instance VarLength Proc_* where varLength _ = 1 instance CustomValue Proc_* where newVarC = liftM fromVar . newVar newArrayVarC = liftM fromArrayVar . newArrayVar readVarC = readVar . head . fromCustomVar writeVarC v x = writeVar (head $ fromCustomVar v) x ifC = if_ -} $(deriveCustomValues) -- Instances for other types. instance (VarLength a, VarLength b) => VarLength (a,b) instance (CustomValue a, CustomValue b) => CustomValue (a,b) instance (VarLength a, VarLength b, VarLength c) => VarLength (a,b,c) instance (CustomValue a, CustomValue b, CustomValue c) => CustomValue (a,b,c)