我如何在Haskell中建模继承? [英] How do I model inheritance in Haskell?

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

问题描述

我试图创建一个由几种不同类型组成的游戏引擎:

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

但是,我正在尝试为所有这些实体实现基本物理。这要求它们都包含一个 pos ::(Double,Double,Double)和一个 velocity ::(Double,Double,Double)

在面向对象的语言中,我会将它实现为如下形式:

<$ p $相机实现物理对象

其中 PhysicalObject code>包含两个属性 pos velocity



我的直接反应是把它们放在同一个类型中:

  data Object = Monster ... |玩家... | NPC ... |相机... 

但是,我担心这可能会导致难以实现相机专用功能,特定光线的功能等等。实际上,除了它们都拥有世界上的物理位置和速度之外,它们几乎没什么共同之处。

是有一种比在每个类型构造函数中定义两个属性更简单的方法吗?

解决方案

我可以想到两种方法 - type班级和镜头。

类型类



  class PhysicalObject m其中
position :: m - > (Double,Double,Double)
velocity :: m - > (Double,Double,Double)

然后,您可以沿着以下几行创建对象的实例 {cameraPosition ::(Double,Double,Double)
,cameraVelocity ::( Double,Double,Double)
}

实例PhysicalObject Camera其中
position = cameraPosition
cameraVelocity = cameraVelocity

以及其他类型。那么任何不需要知道对象细节的函数都可以只需要它的参数为 PhysicalObject 的实例,例如:

  type TimeInterval = Double 

newPosition :: PhysicalObject m => TimeInterval - > m - > (Double,Double,Double)
newPosition dt obj =(x + du * dt,y + dv * dt,z + dw * dt)
其中
(x,y,z) = position obj
(u,v,w)=速度obj

然而,努力编写使用此代码修改对象的函数 - 该类告诉Haskell如何访问对象的位置和速度,而不是如何修改它们。



镜头



另一种选择是转向 lens 库。这对一个人来说有点野兽,但它可以让你编写一些非常自然的代码。首先,有一些样板文件

  { - #LANGUAGE TemplateHaskell# - } 
import Control.Lens

现在定义一些位置和速度数据类型。

 数据Pos = Pos { _posX,_posY,_posZ :: Double} 
data Vel = Vel {_velX,_velY,_velZ :: Double}

实例显示位置show(Pos xyz)= show(x,y ,z)
实例Show Vel其中show(Vel xyz)= show(x,y,z)

现在您使用一些模板Haskell为您的数据类型派生透镜。这将生成类型类 HasPos HasVel ,它们的方法允许您访问和修改任何这些实例的值

  makeClassy''Pos 
makeClassy''Vel

现在定义您的相机类,其中包含一个位置和一个速度。

  data Camera = Camera 
{_cameraPos :: Pos
,_cameraVel :: Vel}派生(显示)

另一个模板Haskell会自动创建函数 cameraPos cameraVel 允许您访问和修改相机的位置和速度。

  makeLenses''相机

最后,声明您的相机是 HasPos HasVel 类,它们的方法默认实现。

  instance HasPos Camera wher e pos = cameraPos 
实例HasVel Camera其中vel = cameraVel

现在我们准备好做一些真正的工作。让我们来定义一个相机示例

  camera = Camera(Pos 0 0 0)(Vel 10 5 0)

一个修改相机的函数,返回一个具有更新位置的新相机,它是

  move ::(HasPos a,HasVel a)=> TimeInterval  - > a  - > a 
move dt obj = obj
& posX +〜dt * obj ^ .velX
& posY +〜dt * obj ^ .velY
& posZ +〜dt * obj ^ .velZ

请注意,这是一个完全通用的函数,用于移动任何对象它具有一个位置和速度 - 它并不完全针对 Camera 类型。它还具有看起来像命令式代码一样的优点!



如果您现在将所有这些加载到GHCI中,您可以看到它在行动中

 >>相机
相机{_cameraPos =(0.0,0.0,0.0),_cameraVel =(10.0,5.0,0.0)}
>>移动0.1相机
相机{_cameraPos =(1.0,0.5,0.0),_cameraVel =(10.0,5.0,0.0)}


I am attempting to create a game engine that is composed of a few different types:

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

However, I'm now trying to implement basic physics for all of these entities. This requires that they each contain a pos :: (Double, Double, Double) and a velocity :: (Double, Double, Double).

In object oriented languages, I would implement it as something like:

Camera implements PhysicalObject

where PhysicalObject contains the two properties pos and velocity.

My immediate reaction was to place them all in the same type:

data Object = Monster ... | Player ... | NPC ... | Camera ...

However, I fear that this might make it tough to implement camera-specific functions, light-specific functions, etc. In reality, they have very little else in common other than the fact that they all possess a physical position and velocity in the world.

Is there a simpler way to do this than defining the two properties inside each type constructor?

解决方案

I can think of two approaches - type classes and lenses.

Type classes

class PhysicalObject m where
  position :: m -> (Double, Double, Double)
  velocity :: m -> (Double, Double, Double)

You would then make instances for the objects along the following lines

data Camera = Camera 
  { cameraPosition :: (Double,Double,Double)
  , cameraVelocity :: (Double,Double,Double)
  }

instance PhysicalObject Camera where
  position       = cameraPosition
  cameraVelocity = cameraVelocity

and similarly for your other types. Then any function which doesn't need to know the details of an object can just require its arguments to be instances of PhysicalObject, for example:

type TimeInterval = Double

newPosition :: PhysicalObject m => TimeInterval -> m -> (Double,Double,Double)
newPosition dt obj = (x + du * dt, y + dv * dt, z + dw * dt)
 where
  (x,y,z) = position obj
  (u,v,w) = velocity obj

However, you will struggle to write functions which modify your objects using this code - the class tells Haskell how it can access the position and velocity of an object, but not how to modify them.

Lenses

The other option is to turn to the lens library. This is a bit of a beast to being with, but it allows you to write some very natural code. First, there's a bit of boilerplate

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

Now define some position and velocity data types. Don't worry about the weird field names prefixed with underscores - we won't be using them.

data Pos = Pos { _posX, _posY, _posZ :: Double }
data Vel = Vel { _velX, _velY, _velZ :: Double }

instance Show Pos where show (Pos x y z) = show (x,y,z)
instance Show Vel where show (Vel x y z) = show (x,y,z)

Now you use a bit of Template Haskell to derive lenses for your data types. This will generate type classes HasPos and HasVel whose methods allow you to access and modify any value that is an instance of those classes.

makeClassy ''Pos
makeClassy ''Vel

Now define your camera class, which includes a position and a velocity.

data Camera = Camera
  { _cameraPos :: Pos
  , _cameraVel :: Vel } deriving (Show)

Another bit of Template Haskell will automatically create functions cameraPos and cameraVel that allow you to access and modify the position and velocity of your camera.

makeLenses ''Camera

Finally, declare that your camera is an instance of both the HasPos and HasVel classes, with a default implementation of their methods.

instance HasPos Camera where pos = cameraPos
instance HasVel Camera where vel = cameraVel

Now we're ready to do some real work. Let's define an example camera

camera = Camera (Pos 0 0 0) (Vel 10 5 0)

A function to modify the camera, returning a new one with an updated position, is

move :: (HasPos a, HasVel a) => TimeInterval -> a -> a
move dt obj = obj
  & posX +~ dt * obj^.velX
  & posY +~ dt * obj^.velY
  & posZ +~ dt * obj^.velZ

Note that this is a completely generic function for moving any object that has a position and velocity - it's not at all specific to the Camera type. It also has the advantage of looking a lot like imperative code!

If you now load all this into GHCI, you can see it in action

>> camera
Camera {_cameraPos = (0.0,0.0,0.0), _cameraVel = (10.0,5.0,0.0)}
>> move 0.1 camera
Camera {_cameraPos = (1.0,0.5,0.0), _cameraVel = (10.0,5.0,0.0)}

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

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