如何处理无限列表中的错误传播和截止条件?

时间:2018-10-18 09:52:36

标签: haskell

给出以下定义

-- 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]]

3 个答案:

答案 0 :(得分:1)

那不可能。

要确定,resultInfLeft还是Right,则需要处理整个无限列表。

一旦您尝试以非平凡的方式使用它,就需要确定resultInfLeft还是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) ()

还有返回最终结果的流。