haskell - 在 append 下保持 IO 惰性

标签 haskell lazy-evaluation monoids semigroup

我可能一直认为 Haskell 比它更懒惰,但我想知道是否有一种方法可以两全其美......
Data.MonoidData.Semigroup定义 First 的两个变体. monoidal 版本对最左边的非空值进行建模,而 semigroup 版本只是对最左边的值进行建模。

这适用于纯值值,但考虑不纯值:

x = putStrLn "x" >> return 42
y = putStrLn "y" >> return 1337

这两个值的类型都是 Num a => IO a . IO aSemigroup a 时的实例是:
instance Semigroup a => Semigroup (IO a)
  -- Defined in `Data.Orphans'

这意味着可以合并两个 IO (First a)值(value)观:
Prelude Data.Semigroup Data.Orphans> fmap First x <> fmap First y
x
y
First {getFirst = 42}

但是,正如我们所见,xy产生它们各自的副作用,即使 y从来不需要。

这同样适用于 Data.Monoid :
Prelude Data.Monoid> fmap (First . Just) x <> fmap (First . Just) y
x
y
First {getFirst = Just 42}

我想我理解为什么会发生这种情况,因为 SemigroupMonoid实例使用 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 资源的优先列表以获取数据,并使用第一个给我有用响应的列表。但是,我不想执行冗余查询(出于性能原因)。

最佳答案

Alternative MaybeT 的实例monad 转换器返回第一个成功的结果,并且不执行其余的操作。结合 asum 函数,我们可以这样写:

import Data.Foldable (asum)
import Control.Applicative
import Control.Monad.Trans.Maybe

action :: Char -> IO Char
action c = putChar c *> return c

main :: IO ()
main = do
    result <- runMaybeT $ asum $ [ empty
                                 , MaybeT $ action 'x' *> return Nothing
                                 , liftIO $ action 'v'
                                 , liftIO $ action 'z'
                                 ]
    print result

哪里最后action 'z'不会被执行。

我们也可以用 Monoid 编写一个新类型的包装器。模仿 Alternative 的实例:
newtype FirstIO a = FirstIO (MaybeT IO a)

firstIO :: IO (Maybe a) -> FirstIO a
firstIO ioma = FirstIO (MaybeT ioma)

getFirstIO :: FirstIO a -> IO (Maybe a)
getFirstIO (FirstIO (MaybeT ioma)) = ioma

instance Monoid (FirstIO a) where
    mempty = FirstIO empty
    FirstIO m1 `mappend` FirstIO m2 = FirstIO $ m1 <|> m2
Alternative之间的关系和 Monoidthis other SO question 中有解释.

关于haskell - 在 append 下保持 IO 惰性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47120384/

相关文章:

haskell - GHC 什么时候使用共享?

hibernate:LazyInitializationException:无法初始化代理

c++ - 从 C++ 到 Haskell 类和状态

Haskell:改进我的尾递归斐波那契实现

haskell - Nix channel 和 GHC/Hackage 软件包版本

haskell - 这个 Haskell 表达式中的 redexes 是什么?

haskell - 为什么引入严格性的函数称为 seq?

haskell - 当 foldr 与幺半群没有任何关系时, foldMap 怎么能做和 foldr 一样的事情呢?

haskell - 为什么没有 fold' 方法?

Haskell - 包装和解开新型包装器 - 有没有更简单的方法?