2013-09-01 5 views
0

Я пишу библиотеку игр. У меня он работает с иерархией экземпляровИнъекционный тип

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 однако, если существует способ передать его в качестве параметра в мой библиотечный модуль, это тоже будет приемлемым.

EDIT

Так же, как я разместил вопрос, я хоть попробовать

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

... на всякий случай. Конечно, это сработало. Я до сих пор не совсем понимаю, как это происходит. Я был бы признателен за объяснение.

+0

Ваши спецификации для «разрешить пользовательские события» неясны. Поэтому я не могу советовать о правильном способе структурирования этого. Не могли бы Вы уточнить? –

+0

Как я писал, у меня был «data LibEvent = MouseMove Int Int | KeyPress Char | ... ', определенном в моем модуле библиотеки. Прошедшее событие поколение ничего в моей библиотеке фактически не использовало эти события, поэтому я думал, что должен иметь возможность писать 'данные MyEvent = MouseMove Int Int | SomethingRelevantToTheGame | ... 'и функцию' LibEvent -> MyEvent' и чтобы мои экземпляры 'Animation' использовали' MyEvent', как если бы они были определены в библиотеке. –

+0

Я думаю, что событие должно быть классом типа. Вы можете сделать что-то вроде 'instance Event e => Animation (Libwrapper e) e где ...' –

ответ

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 и импортировать это в основной:

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 = ... 
+0

Собственно, просто 'class Animation ae, где event :: a -> e -> Writer [e] a' тоже нехорошо (там нет двусмысленности). –