On Fri, Jun 15, 2012 at 6:38 AM, Corentin Dupont
<corentin.dupont@gmail.com> wrote:
It just bothers me a little that I'm not able to enumerate the events, and also that the user is able to create events with wrong types (like New :: Event String), even if they won't be able to register them.
This can be solved with an explicit export list/smart constructors.
newPlayer :: Event Player
newRule :: Event Rule
(hide the New constructor)
In any case, my thinking was that your original
data Event = NewPlayer | NewRule
was basically trying to "join" the semantics of "new things" with Player and Rule. But the original approach ran into the problem you mention below -- it is difficult to maintain invariants, since the types want to "multiply". So formally, I factored:
data Event = NewPlayer | NewRule ==>
data Event = New (Player | Rule) ==>
data Event d = New -- (since the original event didn't want a Player or Rule value. It witnessed the type relation)
On the other hand, if you want to make sure that a type must be "Handled" before you can issue an Event, you can do:
data (Handled d) => Evend d = New
I'm pretty sure the compiler will complain if you try to make a (New :: Event String). I like this idea better than smart constructors for events, if only because you get to use ScopedTypeVariables.
I also have several unrelated events that use the same type of data, so this would be a problem.
Can you clarify?
Adding more events like
data Event d = NewPlayer | NewRule deriving (Typeable, Eq)
is not correct because I can add wrong events like:
addEvent (NewPlayer :: Event Rule) (H(undefined::(Rule -> IO()))) []
Also one question: I don't understand the "where" clause in your class. If I remove it, it works the same...
Yes, unnecessary where clauses are optional.
Here is my code:
newtype Player = P Int deriving Typeable
newtype Rule = R Int deriving Typeable
data Event d = New deriving (Typeable, Eq)
class (Typeable d) => Handled d where
data Handler d = H (d -> IO ())
data EventHandler = forall d . (Handled d) => EH (Event d) (Handler d)
instance Handled Player
instance Handled Rule
addEvent :: (Handled d) => Event d -> Handler d -> [EventHandler] -> [EventHandler]
addEvent e h ehs = (EH e h):ehs
triggerEvent :: (Handled d) => Event d -> d -> [EventHandler] -> IO ()
triggerEvent e d ehs = do
let r = find (\(EH myEvent _) -> cast e == Just myEvent) ehs
case r of
Nothing -> return ()
Just (EH _ (H h)) -> case cast h of
Just castedH -> castedH d
Nothing -> return ()
h1 :: Player -> IO ()
h1 (P a) = putStrLn $ "Welcome Player " ++ (show a) ++ "!"
h2 :: Rule -> IO ()
h2 (R a) = putStrLn $ "New Rule " ++ (show a)
eventList1 = addEvent (New :: Event Player) (H h1) []
eventList2 = addEvent (New :: Event Rule) (H h2) eventList1
trigger1 = triggerEvent (New :: Event Player) (P 1) eventList2 -- yelds "Welcome Player 1!"
trigger2 = triggerEvent (New :: Event Rule) (R 2) eventList2 --yelds "New Rule 2"
Best,
CorentinOn Fri, Jun 15, 2012 at 12:40 AM, Alexander Solla
<alex.solla@gmail.com> wrote:
On Thu, Jun 14, 2012 at 2:04 PM, Corentin Dupont
<corentin.dupont@gmail.com> wrote:
That look really nice!
Unfortunately I need to have an heterogeneous list of all events with their handlers.
With this test code it won't compile:
test1 = addEvent (New :: Event Player) (H (undefined::(Player -> IO ()))) []
test2 = addEvent (New :: Event Rule) (H (undefined::(Rule -> IO ()))) test1
Right, okay. Heterogenous lists are tricky, but I think we can get away with using ExistentialQuantification, since you seem to only want to dispatch over the heterogenous types. The assumption I made is a big deal! It means you can't extract the d value. You can only apply properly typed functions (your handlers) on it.
{-# LANGUAGE ExistentialQuantification #-}
class Handled data where -- Together with EventHandler, corresponds to your "Data" type
data EventHandler = forall d . (Handled d) => EH (Event d) (d -> IO ()) -- EventHandler takes the place of your (Event d, Handler d) pairs without referring to d.
instance Handled Playerinstance Handled Rule
addEvent :: (Handled d) => Event d -> Handler d -> [EventHandler] -> [EventHandler] -- Every [EventHandler] made using addEvent will be of "correct" types (i.e., preserve the typing invariants you want), but YOU must ensure that only [EventHandler]s made in this way are used. This can be done statically with another type and an explicit export list. We can talk about that later, if this works in principle.
triggerEvent :: (Handled d) => Event d -> d -> [EventHandler] -> IO ()