在追加下保持IO懒 [英] Keeping IO lazy under append

查看:121
本文介绍了在追加下保持IO懒的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我可能一直认为Haskell比现在更加懒惰,但我想知道是否有办法让这两个世界最好......

Data.Monoid Data.Semigroup 定义的两个变体First C>。 monoidal版本模拟最左边的非空值,而半群版本只是模拟最左边的值。



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

  x = putStrLnx>>返回42 
y = putStrLny>>返回1337

这两个值的类型都是 Num a => IO a 。当 a 是:> IO a Semigroup / p>

 实例Semigroup a => Semigroup(IO a)
- 定义于`Data.Orphans'

这意味着可以合并两个 IO(First a)值:

 前奏曲Data.Semigroup Data.Orphans> fmap First x<> fmap首先y 
x
y
首先{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 xy = do
x'< -
case x'of
(Just _) - > return x'
Nothing - > y

使用这两个忽略 y

  Prelude>首先x y 
x
42
前奏> mfirst(fmap Just x)(fmap Just y)
x
只需42

在这两种情况下,都不会打印 y



然后我的问题是:



我可以获得两全其美吗?有没有一种方法可以保留Semigroup或Monoid抽象,但仍然会出现IO?



有没有,例如某种 LazyIO 容器,我可以包装第一个值,以便获得我想要的惰性IO? p>

我之后的实际情况是我想查询数据的IO资源的优先级列表,并使用第一个给我一个有用的响应。然而,我并不想要执行多余的查询(出于性能原因)。

解决方案

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

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

action :: Char - > IO Char
action c = putChar c *>返回c

main :: IO()
main = do
result< - runMaybeT $ asum $ [empty
,MaybeT $ action'x'*> ; return $
,liftIO $ action'v'
,liftIO $ action'z'
]
打印结果

其中最后的行动'z'不会被执行。



我们也可以写一个包含 Monoid 实例的新类型包装器,它模仿 Alternative

  newtype FirstIO a = FirstIO(MaybeT IO a)

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

getFirstIO :: FirstIO a - > IO(可能是a)
getFirstIO(FirstIO(MaybeT ioma))= ioma

实例Monoid(FirstIO a)其中
mempty = FirstIO空
FirstIO m1`mappend `FirstIO m2 = FirstIO $ m1< |> m2



替代 Monoid 这个其他的SO问题


I may have been under the false impression that Haskell is lazier than it is, but I wonder if there's a way to get the best of both worlds...

Data.Monoid and Data.Semigroup define two variations of First. The monoidal version models the leftmost, non-empty value, whereas the semigroup version simply models the leftmost value.

This works fine for pure value values, but consider impure values:

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

Both of these values have the type Num a => IO a. IO a is a Semigroup instance when a is:

instance Semigroup a => Semigroup (IO a)
  -- Defined in `Data.Orphans'

This means that it's possible to combine two IO (First a) values:

Prelude Data.Semigroup Data.Orphans> fmap First x <> fmap First y
x
y
First {getFirst = 42}

As we can see, though, both x and y produce their respective side-effects, even though y is never required.

The same applies for Data.Monoid:

Prelude Data.Monoid> fmap (First . Just) x <> fmap (First . Just) y
x
y
First {getFirst = Just 42}

I think I understand why this happens, given that both the Semigroup and Monoid instances use liftA2, which seems to ultimately be based on IO bind, which is strict, as far as I understand it.

If I dispense with the First abstraction(s), however, I can get lazier evaluation:

first x _ = x

mfirst x y = do
  x' <- x
  case x' of
    (Just _) -> return x'
    Nothing -> y

Using both of those ignores y:

Prelude> first x y
x
42
Prelude> mfirst (fmap Just x) (fmap Just y)
x
Just 42

In both of these cases, y isn't printed.

My question is, then:

Can I get the best of both worlds? Is there a way that I can retain the Semigroup or Monoid abstraction, while still get lazy IO?

Is there, for example, some sort of LazyIO container that I can wrap First values in, so that I get the lazy IO I'd like to have?

The actual scenario I'm after is that I'd like to query a prioritised list of IO resources for data, and use the first one that gives me a useful response. I don't, however, want to perform redundant queries (for performance reasons).

解决方案

The Alternative instance for the MaybeT monad transformer returns the first successful result, and does not execute the rest of the operations. In combination with the asum function, we can write something like:

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

where the final action 'z' won't be executed.

We can also write a newtype wrapper with a Monoid instance which mimics the 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

The relationship between Alternative and Monoid is explained in this other SO question.

这篇关于在追加下保持IO懒的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆