【发布时间】:2018-04-17 15:19:26
【问题描述】:
我可能一直认为 Haskell 比它更懒惰,但我想知道是否有一种方法可以两全其美......
Data.Monoid 和Data.Semigroup 定义了First 的两个变体。 monoidal 版本对最左边的非空值进行建模,而 semigroup 版本只是对最左边的值进行建模。
这适用于纯值值,但考虑不纯值:
x = putStrLn "x" >> return 42
y = putStrLn "y" >> return 1337
这两个值的类型都是Num a => IO a。 IO a 是 Semigroup 实例,而 a 是:
instance Semigroup a => Semigroup (IO a)
-- Defined in `Data.Orphans'
这意味着可以组合两个 IO (First a) 值:
Prelude Data.Semigroup Data.Orphans> fmap First x <> fmap First y
x
y
First {getFirst = 42}
不过,正如我们所见,x 和 y 都会产生各自的副作用,尽管从来不需要 y。
同样适用于Data.Monoid:
Prelude Data.Monoid> fmap (First . Just) x <> fmap (First . Just) y
x
y
First {getFirst = Just 42}
我想我明白为什么会发生这种情况,因为Semigroup 和Monoid 实例都使用liftA2,这似乎最终基于IO bind,这是严格的,据我所知。
但是,如果我放弃 First 抽象,我可以获得更惰性的评估:
first x _ = x
mfirst x y = do
x' <- x
case x' of
(Just _) -> return x'
Nothing -> y
同时使用这两个忽略 y:
Prelude> first x y
x
42
Prelude> mfirst (fmap Just x) (fmap Just y)
x
Just 42
在这两种情况下,y 都不会打印出来。
那么我的问题是:
我可以两全其美吗?有没有一种方法可以保留 Semigroup 或 Monoid 抽象,同时仍然获得惰性 IO?
例如,是否有某种 LazyIO 容器可以包装 First 值,以便获得我想要的惰性 IO?
我所追求的实际情况是,我想查询 IO 资源的优先列表以获取数据,并使用第一个给我有用响应的列表。但是,我不想执行冗余查询(出于性能原因)。
【问题讨论】:
-
您需要 semigroup/monoid 抽象,还是
First抽象(也就是提前退出IO函数)? -
@Zeta 理想情况下,我更愿意保留半群/幺半群抽象,但我愿意接受建议。
-
您可以尝试为
Semigroup a => Semigroup (IO a)编写自己的实例,以满足您正在寻找的行为。 -
@danidiaz 由于
IO包装在FirstIO (launchMissiles *> return Nothing)与IO包装在FirstIO (return Nothing)中不同,我会说不,它们不相等。<>之后的位从不考虑评估。
标签: haskell lazy-evaluation monoids semigroup