简体   繁体   中英

Functional Transformation of a collection of Option<T>

I have a collection of Option<T> instances and want to transform from IEnumerable<Option<T>> to Option<IEnumerable<T>>

If ALL the options have a value, then I want a Some<IEnumerable<T>> with the collected values, if ANY of the items in the collection are None , then I want a None<IEnumerable<T>>

This seems like it would be quite a common functional transformation, but i'm not sure if it exists in mainstream functional libraries, or what it would be called. Seems similar to FlatMap , but not quite, as I don't want to just filter out the None values.

I could implement it myself, but would like to find out if it exists already as a functional construct. Don't mind what language, C#, Scala, Haskell etc.

These language constructs are not initially presented in C#; however, there're multiple functional programming libraries out there for such purposes. My choice would be Paul Louth's language-ext ; the richest FP library I've found for C# so far.

There's more than one 'language family' of functional programming concepts.

In Haskell (and, apparently, Scala) this is called sequence and is part of an abstraction or type class called Traversable ; sequence is a special case of a traversal.

Other language famiilies exist, most notably ML. This is the where the term Option comes from (which in Haskell is called Maybe ). While I'm well-versed in F# (which is one ML dialect), I'm not aware that traverse/sequence has an established terminology there.

Language-ext author here, you simply need to call .Sequence() to reverse the inner and outer monads:

IEnumerable<Option<int>> items = ...;

Option<IEnumerable<int>> result = items.Sequence();

It has the exact behaviour you're looking for of returning None if any item in the sequence is None , and Some otherwise.

You can also use .Traverse(...) to map the results as you go.

As you say you don't mind the language, I have an example here using F#, which has Option<T> out of the box:

let chooseAll source =
    let anyNone = source |> Seq.exists Option.isNone
    if anyNone then None else Some source

let s = [Some 1; Some 2; None]
let result1 = s |> chooseAll // None
let s = [Some 4; Some 5]
let result2 = s |> chooseAll // Some {4;5}

If you are unfamiliar with F#, Seq is a type alias for IEnumerable<T> . Here chooseAll has the signature seq<'a option> -> seq<'a> option , which translating to a C# friendly syntax is IEnumerable<Option<T>> -> Option<IEnumerable<T>> .

You can roll your own extension method that relies on available primitives from System.Linq namespace:

public static Option<IEnumerable<T>> AllSome<T>(this IEnumerable<Option<T>> input)
            where T : class
            => (input.All(o => o.IsSome)) ?
                new Some<IEnumerable<T>>(input.Select(o => (o as Some<T>).Value)) :
                new None<IEnumerable<T>>() as Option<IEnumerable<T>>;

This is based on a throwaway implementation of Option type that I wrote to support this example, feel free to adjust to your flavour of Option:

public class Option<T>
    where T: class
{
    protected T value;
    public bool IsSome => value != default(T);
}

public class Some<T> : Option<T>
    where T: class
{
    public Some(T value) => base.value = value;
    public T Value => base.value;
}

public class None<T> : Option<T>
    where T: class
{

}

I played with this a bit and it seems like f# fold and foldBack might be of use in this case. Here are some samples:

let l1 = [Some 1; Some 2; None]
let l2 = [Some 3; Some 4; Some 5]


let acc (ac: int list option) le = 
    match ac, le with
    | Some ac, Some le -> Some (le::ac)
    | _, _ -> None

//this will reverse the order of elements if all present
let r1 = l1 |> List.fold acc (Some [])
let r2 = l2 |> List.fold acc (Some [])

//the order of elements preserved
let acc2 le (ac: int list option) = acc ac le
let r3 = List.foldBack acc2 l1 (Some [])
let r4 = List.foldBack acc2 l2 (Some [])

//or...
let r3b = l1 |> List.foldBack acc2 <| (Some [])
let r4b = l2 |> List.foldBack acc2 <| (Some [])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM