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
时,monadm
中的状态不会发生突变。但在我看来,在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
)进行往返时,monadm
中的状态不会发生突变,其中往返是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中包含的typea
的值时,一些内部状态都会发生变化
验证尝试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
的可变变量附加到monadm
上”隐喻,那么您可能期望终结器操作在引发异常时能够访问最新的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的方式,它常常让人们感到困惑
在很长一段时间里,德