Haskell 的事件驱动器 - 输入和管理对象状态时出错

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

我正在尝试根据此示例(完美运行)在 Haskell 中使用事件架构: https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications

并且我尝试将此代码应用于更复杂的示例。这个想法是以最自然的方式在 Haskell 中改变一个对象(比如说域):

data Domain =
  Domain (String, World)

... 并向 World 执行多个命令并更改它(或者在元组的第一个参数中得到一条消息)。

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

但是当启动 EventLook 时,例如

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

我得到了这个错误(以我的观点,“Domain (msg, newWorld)”的类型是:Domain,不!?(我也尝试 return (msg, newWorld)没有成功)。

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)

因此,我的想法是通过newWorld来计算新的状态(改变对象的数据)。 我可以添加这个玩具示例。

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)) []

提前致谢!

已编辑: 正如@jpmarinier 所指出的,代码应该 return 只有一个参数,因此:“return (Domain (msg,newWorld))” 应该更好。所以我编辑了与这个正确句子共享的代码。

但在这种情况下我得到了两个错误:

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
   |        ^^^^^^^^^^^^^

事实上,您几乎完成了。

要使函数体 dmUpdate 与其类型一致,需要稍作更改:

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

请注意,return 函数已消失,因为您在这里没有 return 单子类型。

或者,这将是 monadic 版本(在您的其余代码中未使用):

-- 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

旁注:在Haskell中,return这个词是相当不幸的。我觉得应该叫wrap而不是return。与命令式语言不同,return 是一个普通函数,在控制流中不起作用。它的类型是:

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

所以 return 只是 Haskell monadic API 的一个组成部分。例如,在列表 monad 的上下文中,表达式 (return 42) 的计算结果只是 [42].

其余代码编译成功:

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
$