0

我正在写一个游戏库。我让它与实例的层次结构一起工作

class Animation a where
    event :: a -> Event -> Writer [Event] a
    paint :: a -> IO ()

这里event处理一个事件并可能发出新事件供其父级查看(例如,出口Button可以等待MouseClickEvent并发出 a CloseEvent)并进行paint绘制。我的通用用例是

--a user defined Animation, say a button
data MyChild = MyChild
instance Animation Child where
    ... anything

--a library defined Animation which is a composition of other animations
data LibWrapper = LibWrapper (Event -> Writer [Event] LibWrapper) (IO ())
mkWrapper :: (Animation a) => a -> LibWrapper
mkWrapper a = LibWrapper (\ev -> mkWrapper <$> event a ev) (paint a)
instance Animation LibWrapper where
    event (LibWrapper e _) = e
    paint (LibWrapper _ p) = p

--a user defined Animation for which the 'event' and 'paint' will be called 
data MyRoot = MyRoot LibWrapper
instance Animation MyRoot where
    event (MyRoot a) ev = MyRoot <$> event a ev
    paint (MyRoot a) = paint a

game = MyRoot (mkWrapper Child)

现在我想允许自定义事件。那是,

class Animation a e where
    event :: a -> e -> Writer [e] a
    paint :: a -> IO ()

问题是我不能让LibWrapper( instance Animation LibWrapper anyevent) 包含更受限制的MyChild( instance Animation MyChild MyEvent)。我尝试参数化LibWrapper并拥有,instance Animation (LibWrapper event) event但 Haskell 似乎认为这两次出现event无关,我不知道该怎么做。

我也考虑过

class Animation a where
    event :: a e -> e -> Writer [e] (a e)
    paint :: a e -> IO ()

然后它LibWrapper MyEvent包含 a MyChild MyEvent,这很好。但我没有办法再定义instance MyChild MyEvent了,不是吗?

但是,我更愿意MyEvent指定类型MyRoot,如果存在将其作为参数传递给我的库模块的方法,那也是可以接受的。

编辑

就像我发布问题一样,我想尝试

class Animation a e where
    event :: a e -> e -> Writer [e] (a e)
    paint :: a e -> IO ()

...以防万一。当然,它奏效了。我还是不太明白这里发生的类型魔法。我会很感激解释。

4

1 回答 1

1

我可以解释的魔法,为了清楚起见,我将忽略event。编译器看到

module Anim where

class Animation a e where
    paint :: a -> IO ()

module A where

data A1 ; data E1

instance Animation A1 E1 where paint A1 = print "i"

module Main where

import Anim; import A

main = paint A1

“画”应该怎么做?请注意,paint A1没有关于E1.

现在想象我添加模块 B 并将其导入到 main 中:

module B where

import Anim; import A

data E2

instance Animation A1 E2 where paint A1 = print "j"

现在main = paint A1显然无法区分您的意思是哪个实例。Haskell 标准要求添加import Bmodule Main不能影响以前工作代码中使用的实例。所以main不管有没有都被拒绝module B

这类事情是类型和数据“族”的用途(以及较旧的功能依赖项)。这需要多阅读GHC用户手册才能完全掌握。但好处是至少有三种方法可以让你告诉 GHC A1 应该总是暗示 E1。例如:关联类型同义词可以这样工作:

class Animation a where
    type Event a :: *
    event :: a -> Event a -> Writer [Event a] a
    paint :: a -> IO ()

instance Animation A1 where
    Event A1 = E1
    event = ...
    paint = ...
于 2013-09-01T13:51:30.910 回答