为了测试我在
Haskell中的技能,我决定实现你在Land of Lisp / Realm of Racket中找到的第一款游戏. “猜猜我的号码”游戏.游戏依赖于可变状态来运行,因为它不断地更新程序的上限和下限以回归用户所考虑的值.
它有点像这样:
> (guess) 50 > (smaller) 25 > (bigger) 37
现在,这种事情(据我所知)在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)
我现在需要做的就是设计出一条路
>打印初始猜测
>接收要求更小/更大数字的命令
>相应地修改状态
>递归调用函数,以便再次猜测
如何以优雅的方式组合IO和State来实现这一目标?
注意:我知道这可以在不使用状态的情况下实现;但我想让它忠于原作
您可以使用monad变换器组合不同的monad – 在本例中为StateT.您可以通过更改类型签名来使用现有代码以使用StateT:
bigger,smaller :: Monad m => StateT Bound m ()
那么你可以编写一个函数来给出一个状态参数来运行游戏:
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
您使用liftIO将IO动作提升到StateT Bound IO monad,允许您提示输入并读取下一行.
最后,您可以使用runStateT运行游戏:
runStateT game initial