也许我们可以学习不被重视的
mtl
包,将之前建议的两种方法结合起来:声明两个类型构造器(并使它们成为functor),并声明相应的typeclasses/instances。
但这里的技巧是:我们将使用
transformers
中的
Data.Functor.Compose
来组合functors,然后定义额外的“传递”实例,以使内部层的方法在外部层可用。就像
mtl
为monad transformers所做的一样!
首先,一些预备工作:
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
import Data.Functor.Compose
data Camera = Camera
data Light = SpotLight | DirectionalLight
data Object = Monster | Player | NPC
data Vec3 = Vec3C
data Colour = ColourC
数据定义:
data Physical a = Physical a Vec3 Vec3 deriving Functor
data Coloured a = Coloured a Colour deriving Functor
相应的类型类:
class Functor g => FunctorPhysical g where
vecs :: g a -> (Vec3,Vec3)
class Functor g => FunctorColoured g where
colour :: g a -> Colour
基本实例:
instance FunctorPhysical Physical where
vecs (Physical _ v1 v2) = (v1,v2)
instance FunctorColoured Coloured where
colour (Coloured _ c) = c
现在让我们来介绍mtl
灵感启发的技巧——透传实例!
instance Functor f => FunctorPhysical (Compose Physical f) where
vecs (Compose f) = vecs f
instance Functor f => FunctorColoured (Compose Coloured f) where
colour (Compose f) = colour f
instance FunctorPhysical f => FunctorPhysical (Compose Coloured f) where
vecs (Compose (Coloured a _)) = vecs a
instance FunctorColoured f => FunctorColoured (Compose Physical f) where
colour (Compose (Physical a _ _)) = colour a
一个示例值:
exampleLight :: Compose Physical Coloured Light
exampleLight = Compose (Physical (Coloured SpotLight ColourC) Vec3C Vec3C)
您应该能够使用上述值的vecs
和colour
。
编辑:上述解决方案存在一个问题,即访问原始包装值很麻烦。这里是另一个使用共范畴的版本,它允许您使用extract
来获取包装的值。
import Control.Comonad
import Control.Comonad.Trans.Class
import Control.Comonad.Trans.Env
import Data.Functor.Identity
data PhysicalT w a = PhysicalT { unPhy :: EnvT (Vec3,Vec3) w a }
instance Functor w => Functor (PhysicalT w) where
fmap g (PhysicalT wa) = PhysicalT (fmap g wa)
instance Comonad w => Comonad (PhysicalT w) where
duplicate (PhysicalT wa) = PhysicalT (extend PhysicalT wa)
extract (PhysicalT wa) = extract wa
instance ComonadTrans PhysicalT where
lower = lower . unPhy
data ColouredT w a = ColouredT { unCol :: EnvT Colour w a }
instance Functor w => Functor (ColouredT w) where
fmap g (ColouredT wa) = ColouredT (fmap g wa)
instance Comonad w => Comonad (ColouredT w) where
duplicate (ColouredT wa) = ColouredT (extend ColouredT wa)
extract (ColouredT wa) = extract wa
instance ComonadTrans ColouredT where
lower = lower . unCol
class Functor g => FunctorPhysical g where
vecs :: g a -> (Vec3,Vec3)
class Functor g => FunctorColoured g where
colour :: g a -> Colour
instance Comonad c => FunctorPhysical (PhysicalT c) where
vecs = ask . unPhy
instance Comonad c => FunctorColoured (ColouredT c) where
colour = ask . unCol
instance (Comonad c, FunctorPhysical c) => FunctorPhysical (ColouredT c) where
vecs = vecs . lower
instance (Comonad c, FunctorColoured c) => FunctorColoured (PhysicalT c) where
colour = colour . lower
exampleLight :: PhysicalT (ColouredT Identity) Light
exampleLight = PhysicalT . EnvT (Vec3C,Vec3C) $
ColouredT . EnvT ColourC $ Identity SpotLight
很遗憾,它需要更多的样板文件。就我个人而言,我会使用嵌套的
EnvT
变换器,代价是访问不够统一。