给出以下定义
-- data, finite
dataFin :: Either String [[Int]]
dataFin = Right [[1..10]]
-- data, infinite
dataInf :: Either String [[Int]]
dataInf = Right [[1..]]
-- function applied to data
fun :: Int -> Either String Int
fun x = if x < 0 then Left "error" else Right $ x+1
-- next stage of processing, short circuiting on error
ns :: Either String [[Int]] -> (Int -> Either String Int) -> Either String [[Int]]
ns l f = l >>= traverse (traverse f)
-- condition for data cutoff
con :: Int -> Bool
con = (< 5)
-- results of processing
resultFin :: Either String [[Int]]
resultFin = ns dataFin fun
resultInf :: Either String [[Int]]
resultInf = ns dataInf fun
我可以很容易地将截止条件应用于有限结果的元素
λ> (fmap . fmap) (takeWhile con) resultFin
Right [[2,3,4]]
但是对于无限数据,它会挂起:
λ> (fmap . fmap) (takeWhile con) resultInf
鉴于Haskell的懒惰性质,如何对无限数据正确地做到这一点?特别是,考虑到条件con
适用于处理后的数据,我应该在哪里以及如何合并它?
编辑。
我能够开发一个(临时的)解决方案,但是我对实现此目的的“正确”方法仍然非常感兴趣。这是当前版本。定义结合了截止条件的“下一个阶段”功能
nsCond :: Either String [[Int]]
-> (Int -> Either String Int)
-> (Int -> Bool)
-> Either String [[Int]]
nsCond d f c =
d >>= (\ls ->
let
pls = (fmap . fmap) f ls :: [[Either String Int]]
wpls = fmap (takeWhile (\v -> case v of
Left _ -> True
Right x -> c x)) pls :: [[Either String Int]]
in
traverse sequence wpls)
我们拥有无限的数据
λ> nsCond dataInf fun con
Right [[2,3,4]]
答案 0 :(得分:1)
那不可能。
要确定,resultInf
是Left
还是Right
,则需要处理整个无限列表。
一旦您尝试以非平凡的方式使用它,就需要确定resultInf
是Left
还是Right
。
此处尝试确定是Left
还是Right
进行打印。
如果您尝试使用该值,则可能会尝试对其进行模式匹配,这将尝试将其放入WHNF中,这意味着该值将被计算到第一个数据构造函数中,即{{1 }}或Left
。
让我们看一个简化的示例:
Right
以某种不平凡的方式使用infiniteList = [0..]
dataInf = traverse fun infiniteList
将已经挂起,因为dataInf
部分会询问,列表中的每个元素是否都是非负数,要回答这个问题,您必须查看该元素中的每个元素列表。
traverse fun
询问列表的前4个元素,如果无限列表的每个元素都不为负,否则询问在检查无限列表时发生的错误。
答案 1 :(得分:1)
只需将traverse和takeWhile函数组合在一起,即可创建一个新的traverseWhile函数,如下所示
traverseWhile::Monad m =>(a->Bool)->(a->m a)->[a]->m [a]
traverseWhile _ _ [] = pure []
traverseWhile p h (x:xs) = (h x) >>= loop xs
where loop ys y | p y = (:) <$> pure y <*> traverseWhile p h ys
| otherwise = pure []
作为takeWhile函数,当条件为false时,此函数停止构造列表。除此之外,此行为与遍历功能相同,但仅适用于Monad和list。
修改ns函数并添加新参数(Int-> Bool)作为条件,将第二个遍历函数替换为traverseWhile as
ns :: Either String [[Int]]
-> (Int -> Bool)
-> (Int -> Either String Int)
-> Either String [[Int]]
ns l c f = l >>= traverse (traverseWhile c f)
和resultFin,结果Inf为
resultFin :: (Int->Bool)->Either String [[Int]]
resultFin c = ns dataFin c fun
resultInf :: (Int->Bool)->Either String [[Int]]
resultInf c = ns dataInf c fun
现在resultInf充当您的临时解决方案。
此外,如果列表中的负数在5以后,则为
ns (Right $ [[1..10] ++ [-1] ++ [1..]]) con fun
给予
右[[2,3,4]]
5点之前
ns (Right $ [[1..2] ++ [-1] ++ [1..]]) con fun
给予
左“错误”
答案 2 :(得分:0)
让我们退后一步。您正在处理某种形状的东西
type Result e a = Either e [a]
表示错误情况或元素列表(可能是无限的)。如您所见,这并不是您要尝试做的正确形状。相反,您想要的是一列成功或失败结束的元素。
data SuccessStream e a
= Cons a (SuccessStream e a)
| Succeeded
| Failed
有几种惯用的方法可以使用通用软件包来表达这一点。这是一个使用streaming
的选项。
import Streaming
type Result' e a = Stream (Of a) (Either e) ()
这为您提供了一个简单的“升级路径”,以更有趣的效果播放流。例如:
import Control.Monad.Trans.Except
type Result'' e a = Stream (Of a) (ExceptT e IO) ()
还有返回最终结果的流。