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
和/或State
monad中.那就是我被困住的地方.
我似乎无法将我的想法包含在IO
monad和State
monad的组合中,所以我可以获得输入,打印结果和修改状态,所有这些都在同一个函数中.
这是我到目前为止所得到的:
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)
我现在需要做的就是设计出一条路
我如何结合IO
并State
以优雅的方式实现这一目标?
注意:我知道这可以在不使用状态的情况下实现; 但我想让它忠于原作
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)
你问的是有点可能......
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
类型可以很有趣和有用.