我正在写一个游戏库。我让它使用
的实例层次结构class Animation a where
event :: a -> Event -> Writer [Event] a
paint :: a -> IO ()
此处event
处理一个事件并可能为其父级发出新事件(例如,退出Button
可能等待MouseClickEvent
并发出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
,其中包含MyChild MyEvent
,这很好。但我没有办法再定义instance MyChild MyEvent
,是吗?
我更喜欢在MyEvent
类型中指定MyRoot
但是,如果有办法将其作为参数传递给我的库模块,那也是可以接受的。< / p>
修改
就在我发布问题时,我想尝试
class Animation a e where
event :: a e -> e -> Writer [e] (a e)
paint :: a e -> IO ()
......以防万一。当然,它奏效了。我仍然不太明白这里的魔法类型。我很感激解释。
答案 0 :(得分: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 B
添加到module Main
不会影响以前工作的代码中使用的实例。因此main
被拒绝,无论是否有module B
。
这类事物是“家庭”的类型和数据(以及较旧的功能依赖性)。这需要更多地阅读GHC user manual才能完全掌握。但好处是,至少有三种方法让你告诉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 = ...