haskell - 如何在 Haskell 中模拟继承?

标签 haskell interface

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

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

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

在面向对象的语言中,我会将其实现为:

Camera implements PhysicalObject

其中 PhysicalObject 包含两个属性 posvelocity

我的第一 react 是将它们全部放在同一类型中:

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

但是,我担心这可能会使实现特定于相机的功能、特定于光的功能等变得困难。实际上,除了它们都拥有物理位置和速度之外,它们几乎没有其他共同点在世界上。

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

最佳答案

我可以想到两种方法 - 类型类和镜头。

类型类

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

然后,您将按照以下方式为对象创建实例

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

instance PhysicalObject Camera where
  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)
 where
  (x,y,z) = position obj
  (u,v,w) = velocity obj

但是,您将很难使用此代码编写修改对象的函数 - 该类告诉 Haskell 如何访问对象的位置和速度,但不告诉如何修改它们。

镜头

另一个选择是转向 lens图书馆。这有点麻烦,但它允许您编写一些非常自然的代码。首先,有一些样板文件

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

现在定义一些位置和速度数据类型。不要担心带有下划线前缀的奇怪字段名称 - 我们不会使用它们。

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)

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

makeClassy ''Pos
makeClassy ''Vel

现在定义您的相机类别,其中包括位置和速度。

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

Haskell 模板的另一部分将自动创建函数 cameraPoscameraVel,允许您访问和修改相机的位置和速度。

makeLenses ''Camera

最后,声明您的相机是 HasPosHasVel 类的实例,并具有其方法的默认实现。

instance HasPos Camera where pos = cameraPos
instance HasVel Camera where 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 中,您就可以看到它的实际效果

>> 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 - 如何在 Haskell 中模拟继承?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20910331/

相关文章:

c# - 在 C# 中 gRPC\Protobuf 中的反射

haskell - 无法 cabal 安装 hs3(对于 haskell-supercollider)

haskell - 如何使用 Template Haskell 检查多态类型的实例是否存在?

haskell - 通过递归示例理解 Haskell 中的非严格性

c# - 界面最佳实践

Java,匿名内部私有(private)接口(interface)的可访问性

interface - 更改 Gmail 的网络界面

haskell - 反向范围实现

haskell - 数据构造函数中的重复

javascript - 查询 typescript 中的接口(interface)