我用Haskell创造了一个国际象棋游戏,一切似乎都在运作。但是,我试图定义程序的主要功能,以便每次进行移动(以两个位置和一个板作为参数)时,生成的板保留在某处,以便它可以用作下一步行动的论据。代码看起来像这样。
makeMove :: Position -> Position -> Board -> Board
makeMove pos1 pos2 board = ...
我知道do
符号并且对Haskell中的IO有基本的了解,但我仍然不确定如何继续。
答案 0 :(得分:2)
我假设您希望您的游戏具有相对动态并响应输入,因此IO问题。
我将给出一些关于命令式样式命令和IO解释为函数的背景理论,然后在Haskell中查看它,最后从这个角度讨论你的案例。
如果这是你知道的东西,请道歉,但无论如何它可能会有所帮助,或者它可能对其他人有帮助。
在Haskell中,我们显然没有变量的直接变异。但是我们可以考虑(关于状态的函数)的一个(密切)相关的概念 - 在命令式范例中被视为变异变量的命令可以被视为'状态变换器':给定一个状态的函数(该计划,世界,无论如何)输出另一个。
一个例子:
假设我们有一个由单个整数变量a
组成的状态。使用符号x := y
表示'将表达式y的值赋给变量x'。 (在许多现代命令式语言中,这是x = y
编写的,但是为了消除等式关系=
的歧义,我们可以使用稍微不同的符号。)然后命令(称之为C
)
a := 0
可以看作是修改变量a的东西。但是,如果我们对“states
”类型有一个抽象的概念,我们可以将C
的“含义”视为从states
到states
的函数。这有时写成〚C〛
。
所以〚C〛: states -> states
,以及任何州,〚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有点结合这些合而为一。 IO ()
个实体表示纯态修改操作,这些操作没有表达式的含义,IO a
个实体(其中a
不是()
)表示(潜在)状态修改其含义为表达式(如“返回类型”)的行为属于a
类型。
现在,由于IO ()
就像一个命令,我们需要像(;)
这样的东西,实际上,在Haskell中,我们有(>>)
和(>>=)
('绑定运算符' ')就像它一样。我们有(>>) :: IO a -> IO b -> IO b
和(>>=) :: IO a -> (a -> IO b) -> IO b
。对于命令(IO ()
)或命令表达式(IO a
),(>>)
运算符只是忽略返回(如果有),并为您提供按顺序执行这两个命令的操作。另一方面,如果我们关心表达式的结果,则(>>=)
是。第二个参数是一个函数,当应用于command-expression的结果时,它会给出另一个命令/命令表达式,即“下一步”。
现在,由于Haskell没有'可变变量',IORef a
- 类型变量代表一个可变引用变量,代表a
- 类型变量。如果ioA
是IORef a
类型的实体,我们可以readIORef ioA
返回IO a
,该表达式是读取变量的结果。如果x :: a
我们可以writeIORef ioA x
返回IO ()
,则该命令是将值x
写入变量的结果。要创建值为IORef a
的新x
,我们会使用newIORef x
,其中IO (IORef a)
最初包含值IORef a
。{ / p>
Haskell也有x
符号,你提到它,这是上面的一个很好的语法糖。简单地说,
do
如果我们有一些IO实体do a; b = a >> b
do v <- e; c = e >>= \v -> c
(可能是某些用户输入上的简单解析器,或任何适合您的情况),我们可以定义
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
的调用更新IORef Board
的命令,就可以使用此getAMove
。
现在,如果使用以下签名制作适当的函数,可以设计一个简单的主IO循环:
moveIO
答案 1 :(得分:0)
您可以使用递归来建模状态,如下所示:
main :: IO ()
main = do
let initialBoard = ...
gameLoop initialBoard
gameLoop :: Board -> IO ()
gameLoop board | gameOver board = putStrLn "Game over."
| otherwise = do
print board
move <- askUserToMove
let newBoard = applyMove move board
gameLoop newBoard
此处board
已更改&#34;通过计算一个新的并递归调用游戏循环。