在 haskell 中请求输入的函数(在 do 块中打印)

Function asking for Input in haskell (with print in do block)

我是 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 块中打印一行,而不管该函数输出的是什么?有什么问题,我是不是误解了 "do" 的工作原理?

UPDT

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

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

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

Updateboard 尚未实现,因此它只有一些伪代码来安抚编译器

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

基本上我砍掉棋盘的顶部以检查哪些列已满('_' = 空),检查玩家使用 "query" 指定的列的顶部以查看其是否合法移动;如果没有,该过程将重新开始。

Haskell 不使用大括号来限定代码范围。相反,它是通过缩进完成的。 return 与您在命令式编程中发现的典型 return 语句不同。它实际上在做的是将您的值包装在 monad 类型中。您可以在 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)

这是没有做的代码:

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

我建议您查看 >> 和 >>= 的作用。一般来说,你应该避免函数 read 它是一个不安全的函数(如果你输入的是字符串而不是 int 会怎样?)还有其他更安全的函数不假设你已经实现了类型类

monad 的理论非常有趣,值得您深入研究并深入理解。但这里有一个大部分错误的答案,可帮助您开始编码而不必担心所有这些。

有两种东西,动作。操作的类型包含在 IO 中,例如 IO IntIO StringIO (Maybe [Bool])。行动就是你做事的方式 I/O。他们做了一些 I/O,完成后,他们 return 他们包装的类型的值。

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

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

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

advanceHuman b = do 
    input <- query
    ...

绑定左侧的名称(或模式)变为 类型,无论包装在 IO 中 - 在本例中为 Int.

不过,我说的是do构造动作。这意味着 advanceHuman 需要 return 一个动作类型:

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 而你应该使用 <- 因为 query 是一个动作。

希望这对您有所帮助。