Haskell 如何理解MonadUnliftIO&x27;"的要求,;没有有状态的单子;?

Haskell 如何理解MonadUnliftIO&x27;"的要求,;没有有状态的单子;?,haskell,monad-transformers,classy-prelude,Haskell,Monad Transformers,Classy Prelude,虽然略读了一些部分,但我还是没有完全理解核心问题“StateT不好,IO没问题”,只是模糊地感觉到Haskell允许编写不好的StateTmonad(或者在本文的最后一个例子中,MonadBaseControl而不是StateT,我认为) 在haddocks中,必须满足以下法律: askUnliftIO >>= (\u -> liftIO (unliftIO u m)) = m 因此,这似乎是在说,当使用askUnliftIO时,monadm中的状态不会发生突变。但在我看来,

虽然略读了一些部分,但我还是没有完全理解核心问题“
StateT
不好,
IO
没问题”,只是模糊地感觉到Haskell允许编写不好的
StateT
monad(或者在本文的最后一个例子中,
MonadBaseControl
而不是
StateT
,我认为)

在haddocks中,必须满足以下法律:

askUnliftIO >>= (\u -> liftIO (unliftIO u m)) = m
因此,这似乎是在说,当使用
askUnliftIO
时,monad
m
中的状态不会发生突变。但在我看来,在
IO
中,整个世界都可以是状态。例如,我可以读取和写入磁盘上的文本文件

引用

虚假的纯洁我们说WriterT和StateT是纯洁的,从技术上讲他们是纯洁的 是的。但老实说:如果你有一个完全 生活在一个州内,你不会从中受益 你想从纯代码中得到的变异。不妨直言不讳 spade,并接受您有一个可变变量

这让我觉得情况确实如此:对于IO,我们是诚实的,对于
StateT
,我们对可变性并不诚实……但这似乎是另一个问题,而不是上述法律试图说明的问题;毕竟,
monadunlifitio
假设的是
IO
。我在概念上难以理解
IO
比其他东西更具限制性

更新1

> fooIO >> askUnliftIO
5
> fooIOunlift = fooIO >> askUnliftIO
> :t fooIOunlift
fooIOunlift :: IO (UnliftIO IO)
> fooIOunlift
5
睡觉后(有些),我仍然感到困惑,但随着时间的推移,我逐渐变得不那么困惑了。我为
IO
做了法律证明。我意识到
id
在自述中的存在。特别是

instance MonadUnliftIO IO where
  askUnliftIO = return (UnliftIO id)
因此,
askUnliftIO
似乎会在
UnliftIO m
上返回一个
IO(IO a)

Prelude> fooIO = print 5
Prelude> :t fooIO
fooIO :: IO ()
Prelude> let barIO :: IO(IO ()); barIO = return fooIO
Prelude> :t barIO
barIO :: IO (IO ())
回到定律上来,它似乎真的在说,当对转换后的monad(
askUnliftIO
)进行往返时,monad
m
中的状态不会发生突变,其中往返是
unLiftIO
->
liftIO

继续上面的例子,
barIO::IO()
,因此如果我们做
barIO>=(u->liftIO(unliftIO u m))
,那么
u::IO()
unliftIO u==IO()
,然后
liftIO(IO())==IO()
**因此,由于一切基本上都是
id
在引擎盖下的应用,我们可以看到没有任何状态被改变,即使我们正在使用
IO
。至关重要的是,我认为,
a
中的值永远不会运行,也不会因为使用
askUnliftIO
而修改任何其他状态。如果确实如此,然后就像在
randomIO::IO a
的情况下一样,如果我们没有在其上运行
askUnliftIO
,我们将无法获得相同的值。(下面的验证尝试1)

但是,似乎我们仍然可以对其他monad执行相同的操作,即使它们确实维护state。但我也看到,对于某些monad,我们可能无法这样做。考虑一个人为的例子:每次我们访问有状态monad中包含的type
a
的值时,一些内部状态都会发生变化

验证尝试1

> fooIO >> askUnliftIO
5
> fooIOunlift = fooIO >> askUnliftIO
> :t fooIOunlift
fooIOunlift :: IO (UnliftIO IO)
> fooIOunlift
5
到目前为止还不错,但对发生以下情况的原因感到困惑:

 > fooIOunlift >>= (\u -> unliftIO u)

<interactive>:50:24: error:
    * Couldn't match expected type `IO b'
                  with actual type `IO a0 -> IO a0'
    * Probable cause: `unliftIO' is applied to too few arguments
      In the expression: unliftIO u
      In the second argument of `(>>=)', namely `(\ u -> unliftIO u)'
      In the expression: fooIOunlift >>= (\ u -> unliftIO u)
    * Relevant bindings include
        it :: IO b (bound at <interactive>:50:1)
>fooIOunlift>=(\u->unlifio u)
:50:24:错误:
*无法匹配预期的类型“IO b”
实际类型为'IO a0->IO a0'
*可能原因:“unliftIO”用于的参数太少
在表达式中:unlifio u
在“(>>=)”的第二个参数中,即“(\u->unlifio u)”
在表达式中:fooIOunlift>>=(\u->unlifio u)
*相关绑定包括
it::IO b(绑定时间:50:1)
“状态不好,IO正常”

这并不是本文的重点。其思想是,
MonadBaseControl
允许在存在并发和异常的情况下,使用有状态的monad转换器进行一些令人困惑的(通常是不受欢迎的)行为

finally::StateT s IO a->StateT s IO a->StateT s IO a
就是一个很好的例子。如果您使用“
StateT
将类型为
s
的可变变量附加到monad
m
上”隐喻,那么您可能期望终结器操作在引发异常时能够访问最新的
s

forkState::StateT s IO a->StateT s IO ThreadId
是另一个。您可能希望输入的状态修改会反映在原始线程中

lol::StateT Int IO[ThreadId]
lol=do
对于[1..10]$\i->do
forkState$modify(+i)
您可能希望
lol
可以重写为
modify(+sum[1..10])
。但这是不对的。
forkState
的实现只是将初始状态传递给forked线程,然后再也无法检索任何状态修改。
StateT
的简单/通用理解让您无法理解

相反,您必须采用一种更细致的观点,将
StateT s m a
视为“一个转换器,它提供一个
s
类型的线程局部不可变变量,该变量隐式地通过一个计算进行线程化,并且在以后的计算步骤中,可以用相同类型的新值替换该局部变量。”(或多或少是对
s->m(a,s)
的冗长英语复述)有了这样的理解,
最终
的行为变得更加清晰:它是一个局部变量,因此无法在异常中生存。同样,
forkState
也变得更加清晰:它是一个线程局部变量,因此对不同线程的更改显然不会影响任何其他线程

这有时是你想要的,但这通常不是人们编写代码IRL的方式,它常常让人们感到困惑

在很长一段时间里,德