如何中止扫描但保留序列的标志元素:skipToOrDefault

How do I abort a scan but retain the flag element of the sequence: skipToOrDefault

我有一个 Result 序列,我想累积所有 Error 值但中止处理和 return 找到的第一个 Ok 值。具体来说,我想中止处理列表的其余部分。不幸的是,我采用的方法保留了找到的第一个 Ok,但不会中止处理列表的其余部分。

let process : Result<'t, string list> -> Result<'t, string list> =
    let st0 = Error []
    let acc st e =
       match st, e with 
       | Ok _   , _        -> st
       | _      , Ok _     -> e
       | Error v, Error vs -> Error (v ++ vs)
    Seq.scan acc st0
    |> Seq.last

理想情况下,Seq.skipToOrDefaultSeq.takeToOrDefault 方法会很好。

从你的评论中,很明显你想做的是避免迭代整个序列,一旦遇到第一个Ok.

好吧,默认情况下序列已经这样做了(它们是 惰性 ),并且 scan 函数保留了 属性。让我们检查一下:

let mySeq = seq {
    for i in 0..3 do
        printfn "Returning %d" i
        yield i
}

mySeq |> Seq.toList |> ignore
> Returning 0
> Returning 1
> Returning 2
> Returning 3

mySeq |> Seq.take 2 |> Seq.toList |> ignore
> Returning 0
> Returning 1

mySeq 
    |> Seq.scan (fun _ x -> printfn "Scanning %d" x) () 
    |> Seq.take 3
    |> Seq.toList |> ignore
> Returning 0
> Scanning 0
> Returning 1
> Scanning 1

看:在scan之后我们再也看不到“回归2”和“回归3”了。那是因为我们没有迭代整个序列,只有我们需要的部分,由 Seq.take 3.

确定

但是 强制代码中的完整迭代的是 Seq.last。毕竟,为了得到最后一个元素,你需要遍历整个序列,没有别的办法。

但是您可以做的是在需要时通过 Seq.takeWhile 停止迭代。此函数接受一个谓词,并且 return 仅包含谓词为 true 的元素,不包括产生 false:

的第一个元素
mySeq |> Seq.takeWhile (fun x -> x < 2) |> Seq.toList |> ignore
> Returning 0
> Returning 1
> Returning 2
> val it : int list = [0; 1]

你的情况的困难在于你还需要 return 破坏谓词的元素。为了做到这一点,你可以部署一个小技巧:在折叠状态下保留一个特殊标志 stop: bool,最初将其设置为 false,然后立即切换到元素上的 true在您需要停止的地方之后。为了保持这种状态,我将使用一条记录:

let st0 = {| prev = Error []; stop = false |}

let acc (s: {| prev: Result<_,string>; stop: bool |}) x =
    match s.prev, x with
    | Ok _, _ -> {| s with stop = true |} // Previous result was Ok => stop now
    | _, Ok _ -> {| s with prev = x |} // Don't stop, but remember the previous result
    | Error a, Error b -> {| s with prev = Error (a @ b) |}

sourceSequence
    |> Seq.scan acc st0 
    |> Seq.takeWhile (fun s -> not s.stop)
    |> Seq.last
    |> (fun s -> s.prev)

P.S。另请注意,在 F# 中,列表串联是 @,而不是 ++。您来自 Haskell 吗?

我认为这是一个更好的解决方案。然而,对于 Seq.tryPick 是否总是无副作用而不管底层顺序如何,存在一些混淆。对于list,这里需要Seq.tail才能前进...

let rec scanTo (pred:'u -> bool) (acc:'u -> 'a -> 'u) (st0:'u) (ss:'a seq) = seq {
    let q = 
        ss 
        |> Seq.tryPick Some 
        |> Option.bind (acc st0 >> Some)

    match q with
    | None               -> yield! Seq.empty
    | Some v when pred v -> yield v
    | Some v             -> yield v; yield! (scanTo pred acc v (Seq.tail ss))
    }

例如...

let process : Result<'v, string list> seq -> Result<'v, string list> seq = fun aa ->
    let mergeErrors acc e = 
        match acc, e with
        | Error ms, Error m -> Error (m @ ms)
        | _, Ok v           -> Ok v
        | _, Error m        -> Error m

    let st0 = Error []

    let isOk = function 
        | Ok _ -> true 
        | _    -> false

    scanTo isOk mergeErrors st0 aa