注射型

时间:2013-09-01 12:57:49

标签: haskell typeclass

我正在写一个游戏库。我让它使用

的实例层次结构
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 ()

问题是我无法让LibWrapperinstance Animation LibWrapper anyevent)包含更受限制的MyChildinstance Animation MyChild MyEvent)。我尝试了参数化LibWrapperinstance 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 ()

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

1 个答案:

答案 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 = ...