Как мне прервать сканирование, но сохранить флаговый элемент последовательности: skipToOrDefault - PullRequest
0 голосов
/ 14 июля 2020

У меня есть последовательность Result, и я хотел бы накопить все значения Error, но прервать обработку и вернуть первое найденное значение 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.skipToOrDefault и Seq.takeToOrDefault для это.

Ответы [ 2 ]

2 голосов
/ 14 июля 2020

Из ваших комментариев стало ясно, что вы хотели бы избежать итерации по всей последовательности, останавливаясь, как только вы встретите первый 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

Смотрите: мы никогда не видим «Возвращение 2» и «Возвращение 3» после scan. Это потому, что мы не повторяем всю последовательность, а только ту часть, которая нам нужна, как определено Seq.take 3.

Но то, что делает заставляет полную итерацию в вашем коде, это Seq.last. В конце концов, чтобы получить последний элемент, вам нужно перебрать всю последовательность, другого пути нет.

Но что вы можете сделать, так это остановить итерацию, когда вам нужно, через Seq.takeWhile. Эта функция принимает предикат и возвращает только элементы, для которых предикат равен true, за исключением первого, который дает false:

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

Трудность в вашем случае заключается в том, что вам также необходимо вернуть элемент, нарушающий предикат. Для этого вы можете использовать небольшой прием: оставить в своем свернутом состоянии специальный флаг 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)

PS также обратите внимание, что в конкатенации списка F # это @, а не ++. Вы едете из Haskell?

0 голосов
/ 17 июля 2020

Думаю, это лучшее решение. Однако существует некоторая путаница относительно того, всегда ли 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
...