猜猜我的号码,一个单一的头痛

Ele*_*fee 8 haskell state-monad io-monad

为了测试我在Haskell的技能,我决定实现你在Land of Lisp/Realm of Racket中找到的第一款游戏.在"猜猜我的号码"的游戏.游戏依赖于可变状态来运行,因为它经常需要更新程序的上限和下限以回归用户正在考虑的值.

它有点像这样:

> (guess)
50
> (smaller)
25
> (bigger)
37
Run Code Online (Sandbox Code Playgroud)

现在,这种事情(据我所知)在Haskell中完全不可能,从REPL中调用一些修改全局可变状态的函数,然后立即打印结果,因为它违反了不变性原则.因此,所有交互必须存在于IO和/或Statemonad中.那就是我被困住的地方.

我似乎无法将我的想法包含在IOmonad和Statemonad的组合中,所以我可以获得输入,打印结果和修改状态,所有这些都在同一个函数中.

这是我到目前为止所得到的:

type Bound = (Int, Int) -- left is the lower bound, right is the upper

initial :: Bound
initial = (1, 100)

guess :: Bound -> Int
guess (l, u) = (l + u) `div` 2

smaller :: State Bound ()
smaller = do
  bd@(l, _) <- get
  let newUpper = max l $ pred $ guess bd
  put $ (l, newUpper)

bigger :: State Bound ()
bigger = do
  bd@(_, u) <- get
  let newLower = min u $ succ $ guess bd
  put $ (newLower, u)
Run Code Online (Sandbox Code Playgroud)

我现在需要做的就是设计出一条路

  • 打印初始猜测
  • 接收想要更小/更大数字的命令
  • 相应地修改状态
  • 递归调用函数,以便再次猜测

我如何结合IOState以优雅的方式实现这一目标?

注意:我知道这可以在不使用状态的情况下实现; 但我想让它忠于原作

Lee*_*Lee 12

你可以使用monad变换器组合不同的monad - 在这种情况下StateT.您可以通过更改要使用的类型签名来使用现有代码StateT:

bigger, smaller :: Monad m => StateT Bound m ()
Run Code Online (Sandbox Code Playgroud)

那么你可以编写一个函数来给出一个状态参数来运行游戏:

game :: StateT Bound IO ()
game = do
  s <- get
  liftIO $ print (guess s)
  verdict <- (liftIO getLine)
  case verdict of
    "smaller" -> smaller >> game
    "bigger" -> bigger >> game
    "ok" -> return ()
    _ -> (liftIO $ putStrLn $ "Unknown verdict " ++ verdict) >> game
Run Code Online (Sandbox Code Playgroud)

你用liftIO抬的IO动作进入StateT Bound IO单子,让您提示输入和读取下一行.

最后你可以使用runStateT以下方式运行游戏:

runStateT game initial
Run Code Online (Sandbox Code Playgroud)


Car*_*arl 7

你问的是有点可能......

import Data.IORef

makeGame :: IO (IO (), IO (), IO ())
makeGame = do
    bound <- newIORef (1, 100)
    let guess = do
            (min, max) <- readIORef bound
            print $ (min + max) `div` 2

        smaller = do
            (min, max) <- readIORef bound
            let mid = (min + max) `div` 2
            writeIORef bound (min, mid)
            guess

        bigger = do
            (min, max) <- readIORef bound
            let mid = (min + max) `div` 2
            writeIORef bound (mid, max)
            guess

    return (guess, smaller, bigger)
Run Code Online (Sandbox Code Playgroud)

没关系代码中有多少冗余,这只是概念的快速证明.这是一个示例会话:

$ ghci guess.hs 
GHCi, version 7.9.20141202: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( guess.hs, interpreted )
Ok, modules loaded: Main.
*Main> (guess, smaller, bigger) <- makeGame 
*Main> guess
50
*Main> smaller
25
*Main> bigger
37
*Main> 
Run Code Online (Sandbox Code Playgroud)

嵌套IO类型可以很有趣和有用.