是否可以在不使用自定义类型的情况下模拟State的行为?

时间:2015-01-30 14:17:45

标签: haskell monads monad-transformers state-monad io-monad

如果我们有以下两个函数,加法和减法,链接它们以在输入上运行一系列计算很简单:

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

的实施,这两个方面似乎都是荒谬的

我认为这是不可能的。我是对的吗?

注意:我知道这是完全不切实际的,但是经过几个小时的工作后我找不到任何解决办法,这意味着我是正确的,或者我的技能还不够任务。

3 个答案:

答案 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)完全避免这些数据类型,并相应地替换as。它绝对不会那么好。

它看起来像这样:

-- 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中没有任何神奇的东西。它只是抽象出国家线程的一种非常好的方式。

此外,StateTState之间的关系与MaybeTMaybe之间的关系相反:State以{{定义1}}。我们可以看到this type synonym中表达的事实:

StateT

它是the Identity type上的变换器(具有简单的type State s = StateT s Identity 实例)。