哈斯克尔 – 猜猜我的号码,一个单一的头痛

前端之家收集整理的这篇文章主要介绍了哈斯克尔 – 猜猜我的号码,一个单一的头痛前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
为了测试我在 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

猜你在找的设计模式相关文章