如果我们有以下两个函数,加法和减法,链接它们以在输入上运行一系列计算很简单:
add :: Int -> State Int ()
add n = state $ \x -> ((),x+n)
subtract :: Int -> State Int ()
subtract n = state $ \x -> ((),x-n)
manyOperations :: State Int ()
manyOperations = do
add 2
subtract 3
add 5
--etc
result = execState manyOperations 5
--result is 9
如果我们想在每次计算完成后打印出状态,我们使用StateT
monad变换器:
add :: Int -> StateT Int IO ()
add n = StateT $ \x -> print (x+n) >> return ((),x+n)
subtract :: Int -> StateT Int IO ()
subtract n = StateT $ \x -> print (x-n) >> return ((),x-n)
manyOperations :: StateT Int IO ()
manyOperations = do
add 2
subtract 3
add 5
main = runStateT manyOperations 5
-- prints 7, then 4, then 9
是否有可能复制这个"组合计算和打印"没有StateT
或任何自定义数据类型?
据我所知,我可以使用MaybeT IO a
完成IO (Maybe a)
可以执行的所有流程,但这似乎是因为它只是因为它只是嵌套的monads。另一方面,StateT
可能没有其他选择,因为s -> (a,s)
与s -> m (a,s)
根本不同
我只能看到代码可以使用的两个方向:使用State Int (IO ())
或IO (State Int ())
,但考虑到StateT
我认为这是不可能的。我是对的吗?
注意:我知道这是完全不切实际的,但是经过几个小时的工作后我找不到任何解决办法,这意味着我是正确的,或者我的技能还不够任务。
答案 0 :(得分:1)
当然,你可以自己做所有的管道工作。 Monads不会向Haskell添加任何新内容,它们只能实现大量的代码重用和样板缩减。所以你可以用monad做任何事情,你可以用手工做。
manyOperations :: Int -> IO ()
manyOperations n0 = do
let n1 = n0 + 2
print n1
let n2 = n1 - 3
print n2
let n3 = n2 + 5
print n3
如果上述功能中的重复次数对你来说太大了(对我而言!),你可以尝试通过组合计算和打印来减少它。功能,但此时你向后弯腰以避免StateT
。
-- a bad function, never write something like this!
printAndCompute :: a -> (a -> b) -> IO b
printAndCompute a f = let b = f a in print b >> return b
-- seriously, don't do this!
manyOperations n0 = do
n1 <- printAndCompute (+2) n0
n2 <- printAndCompute (-3) n1
n3 <- printAndCompute (+5) n2
return ()
答案 1 :(得分:0)
我不确定这是否是您正在寻找的,但是您可以定义一个操作,该操作将执行您所读取的有状态操作,并在执行操作后打印出状态 -
withPrint :: (Show s) => State s a -> StateT s IO a
withPrint operation = do
s <- get
let (a, t) = runState operation s
liftIO (print t)
put t
return a
然后再做
manyOperations :: StateT Int IO ()
manyOperations = do
withPrint (add 2)
withPrint (subtract 3)
withPrint (add 5)
-- etc
这不会避免使用StateT
,但它会将常规非IO状态操作转换为IO-ful状态操作,因此您不必担心关于细节(除了在您希望打印状态时添加withPrint
。
如果您不希望为特定操作打印状态,则可以定义
withoutPrint :: State s a -> StateT s IO a
withoutPrint operation = do
s <- get
let (a, t) = runState operation s
put t
return a
实际上相当于
import Control.Monad.Morph
withoutPrint :: State s a -> StateT s IO a
withoutPrint = hoist (\(Identity a) -> return a)
使用hoist
中的Control.Monad.Morph
将基础StateT
的monad从Identity
转换为IO
。
答案 2 :(得分:0)
您可以直接使用s -> IO (a, s)
完全避免这些数据类型,并相应地替换a
和s
。它绝对不会那么好。
它看起来像这样:
-- This makes StateIO s act as a shorthand for s -> IO (a, s)
type StateIO s a = s -> IO (a, s)
add :: Int -> StateIO Int ()
add n = \x -> print (x+n) >> return ((),x+n)
sub :: Int -> StateIO Int ()
sub n = \x -> print (x-n) >> return ((),x-n)
manyOperations :: StateIO Int ()
manyOperations = -- Using the definition of (>>=) for StateT
\s1 -> do -- and removing a lambda that is immediately applied
(a, s2) <- add 2 s1
(a, s3) <- sub 3 s2
add 5 s3
result :: IO ((), Int)
result = manyOperations 5
状态必须显式线程化所有操作。这是我们使用StateT
数据类型获得的主要收益:其(>>=)
实例的Monad
方法为我们做了所有这些!但是,我们可以从这个例子中看到StateT
中没有任何神奇的东西。它只是抽象出国家线程的一种非常好的方式。
此外,StateT
和State
之间的关系与MaybeT
和Maybe
之间的关系相反:State
以{{定义1}}。我们可以看到this type synonym中表达的事实:
StateT
它是the Identity
type上的变换器(具有简单的type State s = StateT s Identity
实例)。