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
| ^^^^^^^^^^^^^
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 return
is 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)) []
$ 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
$