我假设您希望您的游戏相对动态并响应输入,因此是 IO 问题。
我会给出一些关于命令式命令和IO解释为函数的背景理论,然后在Haskell中看这个,最后从这个角度谈谈你的案例。
命令式命令的一些背景知识
如果这是您知道的内容,请道歉,但无论如何它可能会有所帮助,或者可能对其他人有所帮助。
在 Haskell 中,我们显然没有直接的变量变异。但是我们可以考虑一个(密切)相关的“状态函数”的概念——命令,在命令式范式中,被视为变异变量,可以被视为“状态转换器”:一个函数,给定一个状态(程序,世界等)输出另一个。
一个例子:
假设我们有一个由单个整数变量a 组成的状态。使用符号x := y 表示“将表达式y 的值赋给变量x”。 (在许多现代命令式语言中,这是写成x = y,但为了消除等式关系= 的歧义,我们可以使用稍微不同的符号。)然后是命令(称为C)
a := 0
可以看作是修改变量a的东西。但是,如果我们有一个“states”类型的抽象概念,我们可以将C 的“含义”看作是从states 到states 的函数。这有时写成〚C〛。
所以〚C〛: states -> states,对于任何状态s,〚C〛s = <the state where a = 0>。还有更复杂的状态转换器作用于更复杂的状态,但原理并不比这更复杂!
用熟悉的分号表示从旧状态转换器制作新状态转换器的重要方法。因此,如果我们有状态转换器C1 和C2,我们可以编写一个新的状态转换器,它“执行C1 然后C2”为C1;C2。这在许多命令式编程语言中都很熟悉。事实上,这种“串联”命令作为状态转换器的意义是
〚C1;C2〛: states -> states
〚C1;C2〛s = 〚C2〛(〚C1〛s)
即命令的组成。所以在某种意义上,在类似 Haskell 的符号中
(;) : (states -> states) -> (states -> states) -> states -> states
c1 ; c2 = c2 . c1
即(;) 是组成它们的状态转换器上的运算符。
Haskell 的方法
现在,Haskell 有一些巧妙的方法可以将这些概念直接引入语言。 Haskell 没有为命令(本身没有类型的状态修饰符)和表达式(根据命令式上下文,也可能允许修改状态并产生值)提供不同的类型,而是在某种程度上结合了这些合而为一。 IO () 实体代表纯粹的状态修改动作,它没有作为表达式的意义,IO a 实体(其中 a 不是 ())表示(潜在的)状态修改动作,其意义为表达式(如'返回类型') 的类型为a。
现在,由于IO () 就像一个命令,我们想要类似(;) 的东西,事实上,在Haskell 中,我们有(>>) 和(>>=)('绑定运算符'),它们的作用就像它一样。我们有(>>) :: IO a -> IO b -> IO b 和(>>=) :: IO a -> (a -> IO b) -> IO b。对于命令 (IO ()) 或命令表达式 (IO a),(>>) 运算符会简单地忽略返回(如果有返回),并为您提供按顺序执行两个命令的操作。另一方面,(>>=) 用于我们是否关心表达式的结果。第二个参数是一个函数,当应用于命令表达式的结果时,它会给出另一个命令/命令表达式,即“下一步”。
现在,由于 Haskell 没有“可变变量”,IORef a-type 变量表示可变引用变量,指向 a-type 变量。如果ioA 是IORef a 类型的实体,我们可以做readIORef ioA,它返回一个IO a,这是读取变量的结果表达式。如果x :: a 我们可以执行writeIORef ioA x,它返回一个IO (),该命令是将值x 写入变量的结果。要创建一个新的IORef a,其值为x,我们使用newIORef x,它给出一个IO (IORef a),其中IORef a最初包含值x。
Haskell 还具有您提到的 do 表示法,这是上面的一个很好的语法糖。很简单,
do a; b = a >> b
do v <- e; c = e >>= \v -> c
你的情况
如果我们有一些 IO 实体 getAMove :: IO (Position, Position)(可能是一些用户输入的简单解析器,或者任何适合您的情况),我们可以定义
moveIO :: IORef Board -> IO ()
moveIO board =
readIORef board >>= \currentState -> -- read current state of the board
getAMove >>= \(pos1, pos2) -> -- obtain move instructions
writeIORef board (makeMove pos1 pos2 currentState) -- update the board per makeMove
这也可以使用do 表示法来编写:
moveIO board = do
currentState <- readIORef board; -- read current state of the board
(pos1, pos2) <- getAMove; -- obtain move instructions
writeIORef board (makeMove pos1 pos2 currentState) -- update the board per makeMove
然后,当您需要基于对getAMove 的调用更新IORef Board 的命令时,您可以使用此moveIO。
现在,如果您使用以下签名制作适当的函数,则可以设计一个简单的主 IO 循环:
-- represents a test of the board as to whether the game should continue
checkForContinue :: Board -> Bool
checkForContinue state = ...
-- represents some kind of display action of the board.
-- could be a simple line by line print.
displayBoardState :: Board -> IO ()
displayBoardState state = ...
-- represents the starting state of the board.
startState :: Board
-- a simple main loop
mainLoop :: IORef Board -> IO ()
mainLoop board = do
currentState <- readIORef board;
displayState currentState;
if checkForContinue currentState then
do moveIO board; mainLoop board
else return ()
main :: IO ()
main = do
board <- newIORef startState;
mainLoop board