【问题标题】:Event Drive with Haskell - error of typing and managing object state带有 Haskell 的事件驱动器 - 键入和管理对象状态的错误
【发布时间】:2021-09-14 11:06:19
【问题描述】:

我正在尝试基于此示例在 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,不!?(我也尝试返回 (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 (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
   |        ^^^^^^^^^^^^^

【问题讨论】:

  • 错误消息显示The function ‘return’ is applied to two arguments。你试过return $ Domain (msg, newWorld)return (Domain (msg, newWorld)) 吗?
  • 是的,其实我尝试了好几件事,但都没有成功(但你是对的),带return (Domain (msg,newWorld)) 的版本应该会更好。不过我得到了这个错误:
  • baseEventDomainProgram.hs:31:17: 错误: • 无法将预期类型“m Domain”与实际类型“Domain”匹配 • 在表达式中:dm 在“dmUpdate”等式中:dmUpdate dm _ = dm • 相关绑定包括dmUpdate :: Domain -> Event -> m Domain(绑定在baseEventDomainProgram.hs:26:1)| 31 | dmUpdate dm _ = dm |
  • 非常感谢您的解释!你完全正确,我误解了“return”命令,我不明白为什么 Haskell 强迫我使用 monad。

标签: haskell events monads drive


【解决方案1】:

事实上,你差不多完成了。

要使函数体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 函数已消失,因为您不会在此处返回一元类型。

或者,这将是 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]

其余代码编译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
$ 

【讨论】:

  • 非常感谢您的解释!你说得对,我误解了“return”命令,我不明白为什么 Haskell 强迫我使用 monad。
猜你喜欢
  • 1970-01-01
  • 2018-02-28
  • 2023-01-05
  • 2019-12-27
  • 1970-01-01
  • 2022-09-24
  • 2014-02-20
  • 1970-01-01
  • 2013-08-14
相关资源
最近更新 更多