如何在Haskell中建模mixins /多个接口? [英] How to model mixins / multiple interfaces in Haskell?

查看:102
本文介绍了如何在Haskell中建模mixins /多个接口?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了关于在Haskell中建模继承的这个问题,它提醒我,我有一个更复杂一点的版本一样的问题。我会从那里采用这个例子,因为它比我自己想象的容易。



假设您的程序包含多种类型:

 数据Camera = Camera ... 
data Light = SpotLight ... | DirectionalLight ...
data Object = Monster ... |玩家... | NPC ...

现在你想实现一些基本的物理,所以你希望它们都有一个位置和速度,比如说某种类型的 Vec3



一种方法是声明一个物理带有 pos 和 vel 函数的类型类型,并使所有类型它的实例。但是这意味着你必须修改所有类型以包含两个 Vec3 s,如果你已经定义了很多好的类型并且你只想粘一点位顶部的功能位。 Chris Taylor建议的基于镜头的解决方案具有相同的问题。



一个让我感觉更加整洁的解决方案是声明一个新类型的构造函数,

  data物理a =物理a Vec3 Vec3 

然后你只需要实现 pos vel 和一个 Functor 实例,您可以保留所有现有的类型声明。



然而......这不是很好。如果您现在想要将对象绘制为蓝色或绿色或紫色,则可以使用颜色来完成相同的操作:

  data a a color a a color a b $ b b 

$ b现在如果你有一个彩色物理相机您必须 fmap 不同的次数,这取决于您是要查看其颜色还是其位置或焦距。并且彩色物理相机应该与物理彩色相机相同,但事实并非如此。所以这不是一个优雅的解决方案。



有没有一种很好的方法可以将不同的功能集合到Haskell中的类型中?一个简单的解决方案,可以在没有语言扩展或者很多样板的情况下工作在普通的老式Haskell中,但是我也乐于学习任何与镜头相关的库,如果这真的是解决这个问题的最好方法。 >

这个关于mixins风格的代码重用的旧问题似乎相关,但恐怕我不能完全理解这个问题或被接受的解决方案。)

解决方案

也许我们可以从未评估的 mtl 软件包,并将先前建议的两种方法:声明两个类型的构造函数(并使它们成为函子)并声明相应的类型类/实例。

但是这里有个诀窍:我们将使用 Data.Functor.Compose from 变形金刚,然后定义附加的传递实例以使内层中的方法可用于外层。就像 mtl 为monad变形金刚做的一样!

首先,一些预备:

  { - #LANGUAGE DeriveFunctor# - } 
{ - #LANGUAGE FlexibleInstances# - }

import Data.Functor.Compose

数据Camera =相机
数据Light = SpotLight | DirectionalLight
data Object = Monster |玩家| NPC

data Vec3 = Vec3C - 虚拟类型
data Color = ColourC - 虚拟类型



data 定义:

 数据物理a =物理a Vec3 Vec3派生Functor 
data彩色a =彩色a派生函子

相应的类型类:

  class Functor g => FunctorPhysical g其中
vecs :: g a - > (Vec3,Vec3)

class Functor g => FunctorColoured g其中
color :: g a - >颜色

基本实例:

 实例FunctorPhysical Physical其中
vecs(Physical _ v1 v2)=(v1,v2)

实例FunctorColoured彩色其中
颜色(彩色_ c )= c

现在 mtl - 启发技巧。 Passthrough实例!

 实例Functor f => FunctorPhysical(Compose Physical f)其中
vecs(Compose f)= vecs f

实例Functor f => FunctorColoured(Compose Colored f)其中
color(Compose f)= color f

实例FunctorPhysical f => FunctorPhysical(Compose Colored f)其中
vecs(Compose(Colored a _))= vecs a

实例FunctorColoured f => FunctorColoured(Compose Physical f)其中
color(Compose(Physical a _ _))= color a

示例值:

  exampleLight ::撰写物理彩色光源
exampleLight =撰写(物理(彩色SpotLight ColourC)Vec3C Vec3C)

您应该可以同时使用 vecs color 加上上述值。



编辑:上述解决方案存在访问原始包装值麻烦的问题。这是一个使用comonads的替代版本,它允许您使用 extract 来获取包装值。

  import Control.Comonad 
import Control.Comonad.Trans.Class
import Control.Comonad.Trans.Env
import Data.Functor.Identity

data PhysicalT wa = PhysicalT {unPhy :: EnvT(Vec3,Vec3)wa}

instance Functor w => Functor(PhysicalT w)其中
fmap g(PhysicalT wa)= PhysicalT(fmap g wa)

instance Comonad w => Comonad(PhysicalT w)其中
duplicate(PhysicalT wa)= PhysicalT(extend PhysicalT wa)
extract(PhysicalT wa)= extract wa

instance ComonadTrans PhysicalT其中
较低=较低。 unPhy

-
data ColouredT w a = ColouredT {unCol :: EnvT Color w a}

实例Functor w => Functor(ColouredT w)其中
fmap g(ColouredT wa)= ColouredT(fmap g wa)

实例Comonad w => Comonad(ColouredT w)其中
重复(ColouredT wa)= ColouredT(延长ColouredT wa)
提取(ColouredT wa)=提取wa

实例ComonadTrans ColouredT其中
较低=较低。 unCol

class Functor g => FunctorPhysical g其中
vecs :: g a - > (Vec3,Vec3)

class Functor g => FunctorColoured g其中
color :: g a - >颜色

实例Comonad c => FunctorPhysical(PhysicalT c)其中
vecs =问。 unPhy

实例Comonad c => FunctorColoured(ColouredT c)其中
color = ask。 unCol

- passthrough实例
实例(Comonad c,FunctorPhysical c)=> FunctorPhysical(ColouredT c)其中
vecs = vecs。较低

实例(Comonad c,FunctorColoured c)=> FunctorColoured(PhysicalT c)其中
color = color。低

- 示例值
exampleLight :: PhysicalT(ColouredT Identity)Light
exampleLight = PhysicalT。 EnvT(Vec3C,Vec3C)$
ColouredT。 EnvT ColourC $身份SpotLight

不幸的是,它需要更多的样板。就个人而言,我只会使用嵌套的 EnvT 变形金刚,代价是不太统一的访问。


I came across this question on modeling inheritance in Haskell and it reminded me that I have a little more complicated version of the same problem. I'll adopt the example from there because it's easier than thinking up my own.

Suppose your program contains a number of types:

data Camera = Camera ...
data Light = SpotLight ... | DirectionalLight ...
data Object = Monster ... | Player ... | NPC ...

Now you want to implement some basic physics, so you want them all to have a position and a velocity, say of some type Vec3.

One way to do this is to declare a Physical typeclass with pos and vel functions, and make all your types instances of it. But that means you have to modify all the types to contain two Vec3s, which is annoying if you have a lot of nice types defined already and you just want to glue a little bit of functionality on top. The lens-based solution suggested by Chris Taylor has the same issue.

A solution that feels neater to me is to declare a new type constructor,

data Physical a = Physical a Vec3 Vec3

Then you only have to implement pos, vel, and a Functor instance once, and you get to keep all your existing type declarations.

However... this doesn't compose very well. If you now want to have the ability to paint your objects blue or green or purple, you might want to do the same thing with colours:

data Coloured a = Coloured a Colour

But now if you have a Coloured Physical Camera you have to fmap a different number of times depending on whether you want to look at its colour or its position or its focal length. And a Coloured Physical Camera ought to be the same thing as a Physical Coloured Camera, but it's not. So this is not an elegant solution.

Is there a nice way to mix in different sets of functionality to types in Haskell? A simple solution that works in plain old Haskell without language extensions or a lot of boilerplate would be ideal, but I'm also open to learning any of the lens-related libraries if that's really the best way to approach the problem.

(This older question on mixins-style code reuse seems related, but I'm afraid I don't fully understand the question or the accepted solution.)

解决方案

Perhaps we could take a cue from the underappreciated mtl package, and combine the two previously suggested approaches: declare two type constructors (and make them functors) and declare corresponding typeclasses/instances.

But here's the trick: we will compose the functors using Data.Functor.Compose from transformers, and then define additional "pass-through" instances to make methods from the inner layers available in the outer layer. Just like mtl does for monad transformers!

First, some preliminaries:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}

import Data.Functor.Compose

data Camera = Camera
data Light = SpotLight | DirectionalLight 
data Object = Monster | Player | NPC

data Vec3 = Vec3C -- dummy type 
data Colour = ColourC -- dummy type

The data definitions:

data Physical a = Physical a Vec3 Vec3 deriving Functor
data Coloured a = Coloured a Colour deriving Functor

The corresponding typeclasses:

class Functor g => FunctorPhysical g where
    vecs :: g a -> (Vec3,Vec3)  

class Functor g => FunctorColoured g where
    colour :: g a -> Colour

The base instances:

instance FunctorPhysical Physical where
    vecs (Physical _ v1 v2) = (v1,v2) 

instance FunctorColoured Coloured where
    colour (Coloured _ c) = c

And now the mtl-inspired trick. Passthrough instances!

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

An example value:

exampleLight :: Compose Physical Coloured Light
exampleLight = Compose (Physical (Coloured SpotLight ColourC) Vec3C Vec3C) 

You should be able to use both vecs and colour with the above value.

EDIT: The above solution has the problem that accessing the original wrapped value is cumbersome. Here is an alternate version using comonads that lets you use extract to get the wrapped value back.

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

-- passthrough instances    
instance (Comonad c, FunctorPhysical c) => FunctorPhysical (ColouredT c) where
    vecs = vecs . lower

instance (Comonad c, FunctorColoured c) => FunctorColoured (PhysicalT c) where
    colour = colour . lower

-- example value
exampleLight :: PhysicalT (ColouredT Identity) Light
exampleLight = PhysicalT . EnvT (Vec3C,Vec3C) $ 
               ColouredT . EnvT ColourC       $ Identity SpotLight

Sadly, it requires even more boilerplate. Personally, I would just use nested EnvT transformers at the cost of less uniform access.

这篇关于如何在Haskell中建模mixins /多个接口?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆