Search code examples
haskelleventsmonadsdrive

Event Drive with Haskell - error of typing and managing object state


I am trying to use an architecture of Events in Haskell based on this example (that works perfectly): https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications

And I try to apply this code to a more complex example. The idea is mutate an object (let's say Domain) in the most natural way to do it in Haskell :

data Domain =
  Domain (String, World)

... and execute several commands to World and change it (or got a message in the first parameter of tuple).

With World a "class" of type:
data World = World {loc :: String, descLlocs :: [(String,String)]}  deriving (Show)

But when a EventLook is launched, and for example

dmUpdate :: Domain -> Event -> Domain
dmUpdate (Domain v) (EventLook) = do 
                                  let msg = fst v
                                  let newWorld = snd v
                                  -- Maybe IO Action !? Is possible !?
                                  return Domain (msg, newWorld)
dmUpdate dm _ = dm

I got this error (that with my point of view, "Domain (msg, newWorld)", is of type: Domain, no!? (I also tried to return (msg, newWorld) without success).

baseEventDomainProgram.hs:30:35: error:
    • Couldn't match type ‘(String, World) -> Domain’ with ‘Domain’
      Expected type: (String, World) -> Domain
        Actual type: (String, World) -> (String, World) -> Domain
    • The function ‘return’ is applied to two arguments,
      but its type ‘((String, World) -> Domain)
                    -> (String, World) -> (String, World) -> Domain’
      has only three
      In a stmt of a 'do' block: return Domain (msg, newWorld)
      In the expression:
        do let msg = fst v
           let newWorld = snd v
           return Domain (msg, newWorld)
   |
30 |                                   return Domain (msg, newWorld)

Therefore, my idea is just pass newWorld to compute the new state (change the data of the object). I can add this toy example.

import System.IO

data Event =
    EventExit            -- User wants to exit
  | EventLook   
  | EventAdd Int         
  deriving(Eq,Show)


data World = World {loc :: String, descLlocs :: [(String,String)]}  deriving (Show)

theWorld = World {loc = "living-room", descLlocs = [("living-room","you are in the living-room. a wizard is snoring loudly on the couch.")
           ,("garden","you are in a beautiful garden. there is a well in front of you.")
           , ("attic", "you are in the attic. there is a giant welding torch in the corner.")]}

data Domain =
  Domain (String, World)


dmUpdate :: Domain -> Event -> Domain
dmUpdate (Domain v) (EventLook) = do 
                                  let msg = fst v
                                  let newWorld = snd v
                                  -- Maybe IO Action !? 
                                  return (Domain (msg, newWorld)) 
dmUpdate dm _ = dm

uiUpdate :: Domain -> IO [Event]
uiUpdate (Domain v) = do
  putStrLn $ "WORLD> " ++ show (fst v)
  input <- read'
  if input == ":quit" then
    return [EventExit]
  else
    return [EventLook]

run :: Domain -> [Event] -> IO ()
run dm [] = do
  events <- uiUpdate dm
  run dm events

run _ (EventExit:_) =
  return ()

run dm (e:es) =
  run (dmUpdate dm e) es


read' :: IO String
read' = putStr "WORLD> "
     >> hFlush stdout
     >> getLine


main :: IO ()
main = run (Domain ("",theWorld)) []

Thanks in advance!

EDITED : As pointed by @jpmarinier the code shoud return only one argument so: "return (Domain (msg,newWorld))" should be better. So I edited the code shared with this correct sentence.

But in this case I got two errors:

baseEventDomainProgram.hs:31:17: error:
    • Couldn't match expected type ‘m Domain’ with actual type ‘Domain’
    • In the expression: dm
      In an equation for ‘dmUpdate’: dmUpdate dm _ = dm
    • Relevant bindings include
        dmUpdate :: Domain -> Event -> m Domain
          (bound at baseEventDomainProgram.hs:26:1)
   |
31 | dmUpdate dm _ = dm
   |                 ^^

baseEventDomainProgram.hs:51:8: error:
    • Couldn't match expected type ‘Domain’
                  with actual type ‘m0 Domain’
    • In the first argument of ‘run’, namely ‘(dmUpdate dm e)’
      In the expression: run (dmUpdate dm e) es
      In an equation for ‘run’: run dm (e : es) = run (dmUpdate dm e) es
   |
51 |   run (dmUpdate dm e) es
   |        ^^^^^^^^^^^^^

Solution

  • In fact, you're almost done.

    There is a slight change required to have the body of function dmUpdate agree with its type:

    import System.IO
    
    data Event =
        EventExit            -- User wants to exit
      | EventLook   
      | EventAdd Int         
      deriving(Eq,Show)
    
    
    data World = World {loc :: String, descLlocs :: [(String,String)]}  deriving (Show)
    
    theWorld = World {loc = "living-room",
                      descLlocs = [("living-room", "you are in the living-room. a wizard is snoring loudly on the couch.")
                    , ("garden", "you are in a beautiful garden. there is a well in front of you.")
                    , ("attic", "you are in the attic. there is a giant welding torch in the corner.")]}
    
    data Domain = Domain (String, World)
    
    
    -- plain version:
    dmUpdate :: Domain -> Event -> Domain
    dmUpdate (Domain v) (EventLook) =
          let  msg = fst v
               newWorld = snd v
          in
              (Domain (msg, newWorld)) 
    dmUpdate dm _ = dm
    

    Note that the return function is gone, as you do NOT return a monadic type here.

    Alternatively, this would be the monadic version (unused in the rest of your code):

    -- monadic version:
    dmUpdateM :: Monad m => Domain -> Event -> m Domain
    dmUpdateM (Domain v) (EventLook) =
        do
          let  msg      = fst v
               newWorld = snd v
          return (Domain (msg, newWorld))
    
    dmUpdateM dm _ = return dm
    
    

    Side note: in Haskell, the word return is rather unfortunate. I think it should be called wrap instead of return. Unlike in imperative languages, return is an ordinary function which plays no role in control flow. Its type is:

    return :: Monad m => a -> m a
    

    So returnis just a component of Haskell monadic API. For example, in the context of the list monad, expression (return 42) evaluates to just [42].

    The rest of the code compiles OK:

    uiUpdate :: Domain -> IO [Event]
    uiUpdate (Domain v) = do
      putStrLn $ "WORLD> " ++ show (fst v)
      input <- read'
      if input == ":quit" then
        return [EventExit]
      else
        return [EventLook]
    
    
    run :: Domain -> [Event] -> IO ()
    run dm [] = do
      events <- uiUpdate dm
      run dm events
    
    run _ (EventExit:_) =
      return ()
    
    run dm (e:es) =
      run (dmUpdate dm e) es
    
    
    read' :: IO String
    read' = putStr "WORLD> "
         >> hFlush stdout
         >> getLine
    
    
    main :: IO ()
    main = run (Domain ("",theWorld)) []
    
    

    Testing:

    $ ghc --version
    The Glorious Glasgow Haskell Compilation System, version 8.8.4
    $ 
    $ ghc q68226112.hs  -o ./q68226112.x
    Linking ./q68226112.x ...
    $ 
    $ ./q68226112.x
    WORLD> ""
    WORLD> ba
    WORLD> ""
    WORLD> :quit
    $