haskell,IO Monoid 关联性被打破了吗?

标签 haskell functional-programming io monads monoids

在 haskell IO 类型中有 Monoid 的实例:

instance Monoid a => Monoid (IO a) where
    mempty = pure empty

如果我有三个 Action 共享一些状态,并通过副作用改变彼此的行为,从 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"

编辑:

我想我不应该给出一个数据库的例子,它很困惑, 更优选的例子:

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 顺序运行, a3a1 .例如,与 Python 这样的命令式语言相比,Haskell 中的 IO a不是计算的某些结果,它是产生a 值的配方 .你实际上可以看到 IO更像是 Python 中的延续,您传递一个最终可以调用它的函数,但您不直接调用它。

mappend函数实现为 liftA2 (<>)对于 Semigroup a => Semigroup (IO a)例如,正如我们在 source code 中看到的那样:

instance Semigroup a => Semigroup (IO a) where
    (<>) = liftA2 (<>)

因此这意味着 mappend实现为:

mappendIO :: Semigroup a => IO a -> IO a -> IO a
mappendIO f g = do
    x <- <b>f</b>
    y <- <b>g</b>
    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 (==) again 将定义一个序列,所以这意味着你的liftA2 (==) l r定义为:

liftA2 (==) l r = do
    x1 <- a1
    x2 <- a2
    x3 <- a3
    y1 <- a1
    y2 <- a2
    y3 <- a3
    pure ((x1 <> x2) <> x3) == (y1 <> (y2 <> y3))

您因此运行 r 之后 l .

如果您使用 State monad,你可以更清楚地知道会发生什么,并验证规则是否被应用。您需要重置 l 之间的状态和 r然而。

关于haskell,IO Monoid 关联性被打破了吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64948364/

相关文章:

python - numpy recfromcsv 和 genfromtxt 跳过数据文件的第一行

haskell - 了解 >>= 中的 "Monad m"

haskell - 如何读取haskell字符串

haskell - ghc 7.4.2,动态调用模块

c++ - 为什么我需要为传递给 Y 组合器的函数指定返回值

scala - 如何将输入数据转换为以下格式? - 通过...分组

haskell - 依赖强制语言的一致性是什么意思?

java - 如何从txt中读取行?

haskell - 无法从上下文中推导出 (Eq a) (...)

scala - 功能设计模式