0

如果我们有以下两个函数,加法和减法,很容易将它们链接起来以对输入运行一系列计算:

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

如果我们想在每次计算完成后打印出状态,我们使用StateTmonad 转换器:

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可以使用 using 执行所有可以执行的过程IO (Maybe a),但这似乎是因为它只是嵌套的 monad。另一方面,StateT可能没有替代方案,因为s -> (a,s)s -> m (a,s)

我只能看到代码的两个方向:使用State Int (IO ())or IO (State Int ()),但考虑到实现StateT

我相信这是不可能的。我对么?

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

4

3 回答 3

1

当然,你可以自己做所有的管道。Monad 不会为 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 ()
于 2015-01-30T16:24:41.547 回答
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. 这只是抽象出状态线程的一种非常好的方法。

此外,和之间的关系与和StateT之间State的关系相反:是根据 定义的。我们可以看到用这种类型的同义词表达的事实:MaybeTMaybeStateStateT

type State s = StateT s Identity

它是类型的转换Identity具有平凡的Monad实例)。

于 2015-01-31T20:09:41.557 回答
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)

使用hoistfromControl.Monad.Morph将底层的 monad 转换为StateTfrom 。IdentityIO

于 2015-01-30T20:29:17.933 回答