Mar*_*ann 5 haskell lazy-evaluation monoids semigroup
我可能一直错误地认为 Haskell 比现在更懒惰,但我想知道是否有办法两全其美……
Data.Monoid并Data.Semigroup定义 的两个变体First。幺半群版本对最左边的非空值建模,而半群版本只是对最左边的值建模。
这适用于纯值,但考虑不纯值:
x = putStrLn "x" >> return 42
y = putStrLn "y" >> return 1337
Run Code Online (Sandbox Code Playgroud)
这两个值都具有类型Num a => IO a。IO a是一个Semigroup实例,当a是:
instance Semigroup a => Semigroup (IO a)
-- Defined in `Data.Orphans'
Run Code Online (Sandbox Code Playgroud)
这意味着可以组合两个IO (First a)值:
Prelude Data.Semigroup Data.Orphans> fmap First x <> fmap First y
x
y
First {getFirst = 42}
Run Code Online (Sandbox Code Playgroud)
我们可以看到,虽然双方x并y产生各自的副作用,即使y是从来没有要求。
这同样适用于Data.Monoid:
Prelude Data.Monoid> fmap (First . Just) x <> fmap (First . Just) y
x
y
First {getFirst = Just 42}
Run Code Online (Sandbox Code Playgroud)
我想我明白为什么会发生这种情况,因为Semigroup和Monoid实例都使用liftA2,这似乎最终基于IO bind,据我所知,这是严格的。
First但是,如果我省去了抽象,我可以得到更懒惰的评估:
first x _ = x
mfirst x y = do
x' <- x
case x' of
(Just _) -> return x'
Nothing -> y
Run Code Online (Sandbox Code Playgroud)
使用这两个忽略y:
Prelude> first x y
x
42
Prelude> mfirst (fmap Just x) (fmap Just y)
x
Just 42
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,y都不会打印。
那么我的问题是:
我可以两全其美吗?有没有办法可以保留半群或 Monoid 抽象,同时仍然获得懒惰的 IO?
例如,是否有某种LazyIO容器可以将First值包装在其中,以便获得我想要的惰性 IO?
我所追求的实际场景是,我想查询数据的 IO 资源的优先列表,并使用第一个给出有用响应的列表。但是,我不想执行冗余查询(出于性能原因)。
Alternativemonad 转换器的实例返回MaybeT第一个成功的结果,并且不执行其余操作。结合函数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
Run Code Online (Sandbox Code Playgroud)
最终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
Run Code Online (Sandbox Code Playgroud)
Alternative和之间的关系在另一个 SO 问题Monoid中进行了解释。