是否可以在不使用 StateT 和 ST 的情况下在 State monad 中使用 IO
Is it possible to use IO inside State monad, without using StateT and ST
在下面的代码中,我管理了一个游戏,它拥有一个链接列表。
在游戏的每一步,我都会更改游戏状态,更新已修改的链接列表。
在学习 State monad 时,我试图将 State
monad 技术应用于此用例。
尽管如此,在每个回合,我都需要使用 getLine
从 IO 获取一条信息
这给出了这样一个代码
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
import Control.Monad
import Control.Monad.State.Strict
import qualified Data.List as List
import qualified Control.Monad.IO.Class as IOClass
type Node = Int
type Link = (Node,Node)
type Links = [Link]
type Gateway = Node
type Gateways = [Gateway]
data Game = Game { nbNodes :: Int, links :: Links, gateways :: Gateways }
computeNextTurn :: State Game Link
computeNextTurn = do
g <- get
inputLine <- IOClass.liftIO getLine -- this line causes problem
let node = read inputLine :: Int
let game@(Game _ ls gs) = g
let linkToSever = computeLinkToSever game node
let ls' = List.delete linkToSever ls
let game' = game{links = ls'}
put game'
return linkToSever
computeAllTurns :: State Game Links
computeAllTurns = do
linkToSever <- computeNextTurn
nextGames <- computeAllTurns
return (linkToSever : nextGames)
computeLinkToSever :: Game -> Node -> Link
computeLinkToSever _ _ = (0,1) -- just a dummy value
-- this function doesnt compute really anything for the moment
-- but it will depend on the value of node i got from IO
但是我在编译时遇到错误:
No instance for (MonadIO Data.Functor.Identity.Identity)
arising from a use of liftIO
如果我尝试使用 liftM
和 lift
.
,我会得到相同类型的错误
我已经阅读了一些建议 StateT
和 ST
的问题,但我还没有掌握。
我想知道我目前使用简单状态的技术是否注定要失败,而且我确实不能使用 State
,但是 StateT
/ ST
?
或者在 State
monad 中是否可以简单地从 getLine
获取值?
正如@bheklilr 在他的评论中所说,您不能使用 State
中的 IO。基本上,原因是 State
(对于 StateT
而 Identity
只是 shorthand)不是魔法,所以它不能使用任何东西超过
- 你已经可以在它的基础 monad 中做些什么,
Identity
State
自己提供的新操作
但是,第一点也暗示了解决方案:如果您将基础 monad 从 Identity
更改为其他一些 monad m
,那么您就可以使用 m
。在您的情况下,通过将 m
设置为 IO
,您就可以开始了。
请注意,如果您的部分计算不需要执行 IO,但需要访问您的状态,您仍然可以通过使它们的类型像
foo :: (Monad m) => Bar -> StateT Game m Baz
然后您可以将 foo
与 StateT Game IO
中的计算组合起来,但它的类型也表明它不可能执行任何 IO(或任何其他特定于 base monad 的操作)。
您还在问题中提到 ST
作为可能的解决方案。 ST
不是 monad 转换器,因此不允许您从某些基本 monad 导入效果。
在下面的代码中,我管理了一个游戏,它拥有一个链接列表。 在游戏的每一步,我都会更改游戏状态,更新已修改的链接列表。
在学习 State monad 时,我试图将 State
monad 技术应用于此用例。
尽管如此,在每个回合,我都需要使用 getLine
这给出了这样一个代码
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
import Control.Monad
import Control.Monad.State.Strict
import qualified Data.List as List
import qualified Control.Monad.IO.Class as IOClass
type Node = Int
type Link = (Node,Node)
type Links = [Link]
type Gateway = Node
type Gateways = [Gateway]
data Game = Game { nbNodes :: Int, links :: Links, gateways :: Gateways }
computeNextTurn :: State Game Link
computeNextTurn = do
g <- get
inputLine <- IOClass.liftIO getLine -- this line causes problem
let node = read inputLine :: Int
let game@(Game _ ls gs) = g
let linkToSever = computeLinkToSever game node
let ls' = List.delete linkToSever ls
let game' = game{links = ls'}
put game'
return linkToSever
computeAllTurns :: State Game Links
computeAllTurns = do
linkToSever <- computeNextTurn
nextGames <- computeAllTurns
return (linkToSever : nextGames)
computeLinkToSever :: Game -> Node -> Link
computeLinkToSever _ _ = (0,1) -- just a dummy value
-- this function doesnt compute really anything for the moment
-- but it will depend on the value of node i got from IO
但是我在编译时遇到错误:
No instance for
(MonadIO Data.Functor.Identity.Identity)
arising from a use ofliftIO
如果我尝试使用 liftM
和 lift
.
我已经阅读了一些建议 StateT
和 ST
的问题,但我还没有掌握。
我想知道我目前使用简单状态的技术是否注定要失败,而且我确实不能使用 State
,但是 StateT
/ ST
?
或者在 State
monad 中是否可以简单地从 getLine
获取值?
正如@bheklilr 在他的评论中所说,您不能使用 State
中的 IO。基本上,原因是 State
(对于 StateT
而 Identity
只是 shorthand)不是魔法,所以它不能使用任何东西超过
- 你已经可以在它的基础 monad 中做些什么,
Identity
State
自己提供的新操作
但是,第一点也暗示了解决方案:如果您将基础 monad 从 Identity
更改为其他一些 monad m
,那么您就可以使用 m
。在您的情况下,通过将 m
设置为 IO
,您就可以开始了。
请注意,如果您的部分计算不需要执行 IO,但需要访问您的状态,您仍然可以通过使它们的类型像
foo :: (Monad m) => Bar -> StateT Game m Baz
然后您可以将 foo
与 StateT Game IO
中的计算组合起来,但它的类型也表明它不可能执行任何 IO(或任何其他特定于 base monad 的操作)。
您还在问题中提到 ST
作为可能的解决方案。 ST
不是 monad 转换器,因此不允许您从某些基本 monad 导入效果。