![](/img/trans.png)
[英]Is it possible for a non IO monad to violate Associativity in Haskell?
[英]haskell, is IO Monoid associativity broken?
在 haskell IO 类型中有 Monoid 的实例:
instance Monoid a => Monoid (IO a) where
mempty = pure empty
如果我有三个共享某个状态的操作,并通过副作用改变彼此的行为,从 IO 类型的角度来看,这可能会导致违反关联律:
a1:: IO String
a2:: IO String
a3:: IO String
(a1 mappend
a2) mappend
a3 /= a1 mappend
(a2 mappend
a3)
例如,如果 a1,a2,a3 请求字符串中的当前时间,或者 IO 包含一些计算请求编号的 DB。 这意味着它可以是:
(a1 `mappend` a2) `mappend` a3 == "1"++"2"++"3"
a1 `mappend` (a2 `mappend` a3) == "3"++"1"++"2"
编辑:
我想我不应该用 db 举一个例子,它很困惑,更喜欢的例子:
a1 = show <$> getUnixTime
a2 = show <$> getUnixTime
a3 = show <$> getUnixTime
l = (a1 `mappend` a2) `mappend` a3
r = a1 `mappend` (a2 `mappend` a3)
liftA2 (==) l r
**False**
那么为什么 IO 类型是幺半群,如果它可以打破结合律呢? 或者我错过了什么?
a1 `mappend` (a2 `mappend` a3)
没有按照a2
、 a3
和a1
的顺序运行。 相反如Python当务之急语言例如,在Haskell一个IO a
不是计算的一些结果是,产生的值的配方a
。 你实际上可以看到一个IO
更像是 Python 中的延续,你传递一个函数,这样最终可以调用它,但你不直接调用它。
mappend
函数被实现为Semigroup a => Semigroup (IO a)
实例的liftA2 (<>)
,正如我们在源代码中看到的:
instance Semigroup a => Semigroup (IO a) where (<>) = liftA2 (<>)
因此,这意味着mappend
实现为:
mappendIO :: Semigroup a => IO a -> IO a -> IO a
mappendIO f g = do
x <- f
y <- g
pure (x <> y)
所以它运行f
之前g
。
如果我们现在查看(a1 `mappend` a2) `mappend` a3
,我们会看到:
(a1 `mappend` a2) `mappend` a3 = do
x <- do
x1 <- a1
x2 <- a2
pure (x1 <> x2)
y <- a3
pure (x <> y)
这相当于:
(a1 `mappend` a2) `mappend` a3 = do
x1 <- a1
x2 <- a2
x3 <- a3
pure ((x1 <> x2) <> x3)
如果我们再看a1 `mappend` (a2 `mappend` a3)
那么这等价于:
a1 `mappend` (a2 `mappend` a3) = do
x <- a1
y <- do
y1 <- a2
y2 <- a2
pure (y1 <> y2)
pure (x <> y)
这相当于:
a1 `mappend` (a2 `mappend` a3) = do
x1 <- a1
x2 <- a2
x3 <- a2
pure (x1 <> (x2 <> x3))
由于x1 <> (x2 <> x3)
等价于(x1 <> x2) <> x3
,因此这将在两个项目中返回相同的结果。
至于你的测试:
l = (a1 `mappend` a2) `mappend` a3
r = a1 `mappend` (a2 `mappend` a3)
liftA2 (==) l r
False
请注意, liftA2 (==)
将再次定义一个序列,这意味着您的liftA2 (==) lr
被定义为:
liftA2 (==) l r = do
x1 <- a1
x2 <- a2
x3 <- a3
y1 <- a1
y2 <- a2
y3 <- a3
pure ((x1 <> x2) <> x3) == (y1 <> (y2 <> y3))
因此,您在l
之后运行r
。
如果您使用State
monad,您可以更清楚地说明会发生什么,并验证是否应用了规则。 但是,您需要重置l
和r
之间的状态。
您不能使用liftA2 (==)
来有意义地比较 IO 值:这种比较关系甚至不是自反的!
事实上,如果我们运行
a1 = show <$> getUnixTime
liftA2 (==) a1 a1
可能会得到False
结果,因为两次调用getUnixTime
之间经过了时间,因此返回的值可能不同。
如果你定义a1
来返回某个随机数生成器的值,这就更加清楚了。 调用两次几乎总是会产生不同的结果。
另一个例子: liftA2 (==) getLine getLine
如果用户输入两个不同的行, liftA2 (==) getLine getLine
可以返回 false。
当我们说ioAction1
等于ioAction2
我们的意思是如果在完全相同的上下文中执行它们将具有相同的效果。 这是不一样的,作为执行一个接一个的动作并比较结果。
但是,精确定义“相同的 IO 效果”很棘手,因为我们通常希望忽略性能差异。 例如return () >> print True
可能比print True
稍微慢一点,如果不进行优化,我们仍然希望将这两个动作视为具有相同的效果。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.