我有一个基本上具有以下功能的计算:
f :: [a] -> ([b],Bool)
实际上可以写这个函数
f = foldr h ([],False) . map g
where h (b,bool) (bs,boolSoFar) = (b:bs,bool || boolSoFar)
其中g :: a -> (b,Bool)
是一些耗费大量时间的函数。此外,f通常在小列表中调用,因此尝试并行计算地图似乎很有趣。这可以通过Control.Parallel.Strategies parMap来完成。所以现在我们使用
f = foldr h ([],False) . parMap rseq g
where h (b,bool) (bs,boolSoFar) = (b:bs, bool || boolSoFar)
这一切都很棒。现在,您将注意到可以在f
的第一个定义中执行顺序优化。也就是说,我可以使用map-fold fusion将其作为单个折叠编写,因此在列表中循环一次。但是,我失去了做并行的好处。
现在,有人可能会说在f
的第二个定义中,再次循环列表并不是那么糟糕,所以为什么不这样做呢。我想我的想法是,如果Haskell有可变变量,那么可以在map的主体中更新这个布尔变量(我想你必须锁定并解锁它)。做这样的事情有什么建议吗?
答案 0 :(得分:1)
这最终会成为懒惰作家Applicative
下的一个遍历,作者状态为Bool
,因为(False, (||))
形成一个幺半群。您还需要unamb
个包,因此您可以在g
的任何并行调用返回True
时第一次获取该值。
import Control.Parallel.Strategies
import Data.Unamb
newtype EvalWB a = EvalWB { runEvalWB :: Eval (a, Bool) }
instance Functor EvalWB where
fmap f (EvalWB m) = EvalWB $ fmap (\ ~(a, b) -> (f a, b)) m
instance Applicative EvalWB where
pure a = EvalWB $ pure (a, False)
EvalWB mf <*> EvalWB ma = EvalWB $ (\ ~(f, bf) ~(a, ba) -> (f a, por bf ba)) <$> mf <*> ma
然后你有
f :: [a] -> ([b], Bool)
f l = runEval $ runEvalWB $ traverse (\a -> EvalWB $ rpar $ g a) l
这会并行传递整个列表,懒惰地累积值和标志。它使用por
在第一个True
返回时短路。
答案 1 :(得分:-1)
你不能使用State Monad吗?更改函数f
:
f :: [a] -> ([b], Bool)
为:
f :: [a] -> State Bool [b]
你只需要通过一次折叠你的名单来更新你的州的价值,不是吗?不过,我不确定你是否可以将它应用于并行的东西。我对Haskell的了解有些局限。