将打印文本的函数作为参数传递,在 Haskell 中的新状态后执行它

Passing as parameter a function that prints a text, executing it after new state in Haskell

[EDIT3:包含几乎完整代码的单个文件:

https://github.com/agutie58/landOfLispInHaskell/blob/main/exampleLoLTextGameHaskell.hs

EDIT2:实际游戏的例子。]


[原题]
我有一个基于 Domain 的函数 run 和一个事件列表:

大多数事件修改 Domain:

data Domain =   Domain (String, World) deriving (Show)
data World = World {loc :: String, descSites :: [(Key,String)], mapSites :: [(Key, [Lloc])], objects:: [Key], siteObjects::[(Key,String)]}  deriving (Show)


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

我想关注的部分是run (dmUpdate dm e) es,其中dmUpdate dm ereturn是一个Domain值:

此函数 dmUpdate 运行良好的一个示例是:

dmUpdate :: Domain -> Event  ->  Domain

dmUpdate (Domain v) (EventLook) =  look (snd v) 
                                               
dmUpdate (Domain v) (EventWalk direction) =  walk direction (snd v) 

dmUpdate dm _ _ = dm 

其中:

look :: World -> Domain
walk :: String -> World -> Domain
-- etc.

我想呈现(打印到控制台)新状态的结果。例如:

dmUpdate (Domain v) (EventLook) =  do let newDomain = look (snd v) 
                                          putStr (fst newDomain)
                                          newDomain

但它不起作用。我尝试计算一个新的世界状态,然后做 I/O 然后尝试 return newDomain 作为参数。

我想像这样传递一个函数:

run dm (e:es) =
  run (dmUpdate dm e renderMsg) es
  where renderMsg txt = (putStr txt) >> (hFlush stdout)

...为了做类似的事情:

-- dmUpdate :: dmUpdate :: Domain -> Event -> (String -> IO ()) -> IO () -> Domain
dmUpdate (Domain v) (EventLook) (renderMsg) =  let newDomain = look (snd v) 
                                               renderMsg (fst newDomain)

但是不起作用。

有什么想法!?提前致谢!


[EDIT1]
我也试过了:

  dmUpdate :: Domain -> Event  -> IO Domain
    dmUpdate (Domain v) (EventLook) =  do let newDomain = look (snd v) 
                                          putStr (fst newDomain)
                                          newDomain
                                                                                        
    
    dmUpdate dm _ _ = () dm 

...但我收到了这条消息:

[2 of 2] Compiling Main             ( textGameMain.hs, interpreted )

textGameMain.hs:25:1: error:
    Equations for ‘dmUpdate’ have different numbers of arguments
      textGameMain.hs:(25,1)-(27,47)
      textGameMain.hs:33:1-23
   |
25 | dmUpdate (Domain v) (EventLook) =  do let newDomain = look (snd v) 
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

textGameMain.hs:70:8: error:
    • Couldn't match expected type ‘Domain’
                  with actual type ‘IO 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
   |
70 |   run (dmUpdate dm e ) es

于是,我尝试改变run:

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

但我没能成功...:S


[EDIT2]
所以,我的代码是这样做的(它基于 Land of Lisp,并且是对 Haskell 的改编):


    *主要>主要
    世界>看
    你在客厅里。一个巫师在沙发上大声打鼾。
    从这里向西有门。
    有梯子从这里上楼。
    你看到地板上有威士忌。
    你看到地板上有一个水桶。
    
    世界>向西走
    世界>看
    你在一个美丽的花园里。你面前有一口井。
    从这里向东有门。
    你看到地板上有一只青蛙。
    你看到地板上有一条链子。


[EDIT3]
(完整代码 - 事实上,为了简单起见,我将两个文件折叠成一个文件......可以通过其他方式添加文件吗!?)

https://github.com/agutie58/landOfLispInHaskell/blob/main/exampleLoLTextGameHaskell.hs

ghci exampleLoLTextGameHaskell.hs
main

(...然后,EDIT2...)

您的代码远未完成,包含很多 - 对于您的问题 - 不必要的部分。我复制了你的代码,简化了一点,并做了一些猜测。这是我的“固定”版本。

module Tmp where
import Text.Read (readMaybe)

{-| Be clear about your types. Introducing type alisases helps others read your code.
 I simply guessed that the string-part of the Domain is the UI state -}
type UIState = String
data Domain = Domain (UIState,World) deriving Show
data World = World {loc:: Int} deriving Show

-- | I Have only implemented two directions, so that this example is easy to work with.
data Dir = L | R deriving (Read, Show)

{- | These were the event types that you had in your code. I elaborated them a little. 
By using the "deriving (Read)" we get a low-code input mechanism, but you should probalbly write
your own input parser
-} 
data Event = EventExit | EventWalk Dir| EventLook deriving (Read)

-- | The run-loop now has TWO steps. Render UI and get new events. Process events. Finally recurse.
run :: Domain -> [Event] -> IO ()
run dm [] = uiUpdate dm >> getAction >>= run dm
run _ (EventExit:_) = return ()
run dm (e:es) = run (dmUpdate dm e) es

{-| Update the domain when a single event acts on it -}
dmUpdate :: Domain -> Event -> Domain
dmUpdate (Domain v) (EventLook) =  look (snd v) 
dmUpdate (Domain v) (EventWalk direction) =  walk direction (snd v)
dmUpdate dm _ = dm

look w = Domain ("You are at coordinate " ++ (show .loc $ w), w)
walk L w@World{loc=l}= Domain ("You went left", w{loc=l-1})
walk R w@World{loc=l} = Domain  ("You went right", w{loc=l+1})

{-| Present the "output" that the domain holds for us -}
uiUpdate :: Domain -> IO ()
uiUpdate dm = do
    let Domain (usState,s) = dm
    putStrLn usState

{-| Ask user for input. Only a single event is collected. -}
getAction :: IO [Event]
getAction = do
    putStrLn "What do you want to do? Choose between EventExit | EventWalk R | EventWalk L | EventLook"
    act <- readMaybe <$> getLine
    case act of 
        Nothing -> putStrLn "Not a valid action" >> getAction
        Just evt -> pure [evt]


main :: IO ()
main = run  (Domain ("",World 0)) [EventLook]

最后,您可能想查看 StateT,这样您就可以抽象出一直传递的域对象。但我想这超出了这个问题的范围。