0

我对haskell真的很陌生,我正在尝试实现一个简单的connect4游戏,当我试图让玩家输入一个新动作时,我想提示他这样做。这是我拥有的相关代码:

advanceHuman :: Board -> Board
advanceHuman b = do
     let column = query 
     if (snd((possibleMoves b)!!(column-1)) == cha)
         then updateBoard b p1 column
         else advanceHuman b

query :: Int
query = do {
    print ("escoge una columna vacia") ; -- choose empty column
    input <- getLine ;
    return (read input) }

如您所见,我尝试提示玩家,获取他的答案并将其传递给其他功能(假设玩家将合作并输入有效数字)。但是,这是我尝试编译时收到的错误消息

    * Couldn't match expected type `Int' with actual type `IO b0'
    * In a stmt of a 'do' block: print ("escoge una columna vacia")
      In the expression:
        do print ("escoge una columna vacia")
           input <- getLine
           return (read input)
      In an equation for `query':
          query
            = do print ("escoge una columna vacia")
                 input <- getLine
                 return (read input)
   |
47 |     print ("escoge una columna vacia") ;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

据我所知,我应该能够在 do 块中打印一行,而不管该函数输出什么,对吗?什么问题,我是否误解了“做”的工作原理?

UPDT

根据要求,这些是涉及的其他功能

possibleMoves:: Board -> Board 
possibleMoves b = take sx b

updateBoard:: Board -> String -> Int -> Board
updateBoard b pl col = b

更新板还没有实现,所以它只有一些虚拟代码来安抚编译器

p1 cha 和 sx 是预先声明的全局常量

基本上,我切掉我的棋盘顶部以检查哪些列已满('_' = 空),检查玩家用“查询”指定的列的顶部,看看它是否合法;如果没有,则该过程再次开始。

4

2 回答 2

3

monad 的理论非常有趣,值得你花时间去深入了解它。但这里有一个大部分错误的答案,可以帮助您开始编码而不用担心所有这些。

有两种东西,价值观行动。动作的类型包含在IO, IO Int,IO StringIO (Maybe [Bool])。操作是您进行 I/O 的方式。它们执行一些 I/O,完成后,它们返回它们包装的类型的值。

动作是用 构造的do,通常最后一行有return <value>(或者是另一个动作,在这种情况下它使用那个动作的返回值)。所以你query是一个动作:

query :: IO Int   -- notice the IO
query = do
    print ("escoge una columna vacia")
    input <- getLine
    return (read input)

使用动作的方式是使用绑定它们<-,你已经用getLine. 这只能在一个do块中完成。所以当你想使用queryin 时advanceHuman,你需要绑定它:

advanceHuman b = do 
    input <- query
    ...

绑定左侧的名称(或模式)变成了任何被包装的类型的IO——在本例Int中。

但是,我说的是do构造动作。这意味着advanceHuman需要返回一个动作类型:

advanceHuman :: Board -> IO Board
advanceHuman b = do 
    input <- query
    ...

在 do 块中唯一可以是行的是动作,无论是否绑定到值,并且return <value>(事实证明,这也是一个动作)。

您必须在使用它们的值之前绑定动作。例如,如果你有

getX :: IO Int
getY :: IO Int

那么你不能说getX + getY得到他们的总和。你不得不说do { x <- getX; y <- getY; return (x + y) } (或者liftA2 (+) getX getY,但我们不要超越自己)。

如果要将名称绑定到而不是操作,请let改用。所以在advanceHuman你使用的let时候应该使用<-becausequery是一个动作。

希望这可以帮助。

于 2020-04-25T06:26:19.980 回答
1

Haskell 不使用大括号来定义代码范围。相反,它是通过缩进完成的。return 也不像您在命令式编程中发现的典型 return 语句。它实际上在做的是将你的值包装在一个单子类型中。您可以在 GHCI 中检查:

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

do符号是组成一元动作的一种方式。如果没有很多我不会在这里讨论的理论,很难解释这意味着什么。(我建议你拿起一本haskell书)。它只是语法糖,没有它你就可以过关(我将在下面展示)。

至少要修复您的代码:

query :: (IO Int) 
query = do 
    print ("escoge una columna vacia")
    input <- getLine
    return (read input)

IO 是一种在应用 Int 时成为类型的类型。类型是(IO Int)

这是没有do的代码:

module Examples where
query :: (IO Int) 
query = 
    print ("escoge una columna vacia") >>
    getLine >>= (\x ->
    return (read x))

我建议您查看 >> 和 >>= 的作用。一般来说,您应该避免使用read它是不安全函数的函数(如果您在其中键入字符串而不是 int 怎么办?)还有其他更安全的函数不假定您实现了类型类

于 2020-04-25T03:53:22.467 回答