5

I have the following:

type union1 =
    | Case1 of string
    | Case2 of int

let union1s = seq { for i in 1..5 do yield case2 i }

How do I change union1s to a sequence of type seq<int>?

Something like:

let matchCase item =
    match item with
    | Case1 x -> x
    | Case2 x -> x

let case2s = Seq.map matchCase union1s

This attempt does not work because matchCase can not return two different types.

The suggested answers have the same problem (if I understand correctly)

let matchCaseOpt = function
    | Case1 x -> Some x
    | Case2 x -> Some x
    | _ -> None

let case2s = Seq.choose matchCaseOpts unions1s

The expression Some x expects expects type Option string in the match for Case2

I have solved my particular use-case by using a DU of sequences.

type Union1s =
    | Case1s of seq<string>
    | Case2s of seq<int>    
Remko
  • 754
  • 3
  • 15

3 Answers3

8

You are assuming your sequence contains not a single case1, so if this is not true you need to throw an exception.

let matchCase item =
    match item with
    | Case1 x -> failwith "Unexpected Case1"
    | Case2 x -> x

let case2s = Seq.map matchCase union1s

An alternative approach, if you are not sure the sequence contains always the same case is to use an Option

let matchCase item =
    match item with
    | Case1 x -> None
    | Case2 x -> Some x

Then it depends on how you will handle these cases, you can just filter out the None values using Seq.choose instead of Seq.map as shown on the other answer.

Which approach to follow depends if you consider having an argument with Case1s an exceptional case or part of the logic of your program. There was a question regarding this F#: Some, None, or Exception? recently.

Using a DU of sequences is correct if you are not mixing Cases, that way your DU Type restrict your domain to the actual cases.

Community
  • 1
  • 1
Gus
  • 25,839
  • 2
  • 51
  • 76
  • Hi, thanks. However sometimes the seq contains items of only case1 – Remko Sep 04 '13 at 07:47
  • Then use the alternative version and check the results. You can decide then what to do with the NONEs. May be filter them out with Seq.choose as shown on the other answer. It really depends if it's an Exceptional case or not. – Gus Sep 04 '13 at 07:52
3

As an alternative:

let matchCaseOpt item =
    match item with
    | Case2 x -> Some(x)
    | _ -> None

let case2s = union1s |> Seq.choose matchCaseOpt

This version will drop any cases other than Case2, where Gustavo's solution will throw an exception if those occur. Which solution is best depends on your exact requirements, of course.

Note that this solution uses Seq.choose rather than Seq.map.

Luc C
  • 1,133
  • 8
  • 22
2

You could try the following reflection-based, general purpose implementation:

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Reflection

let filterUnionCases (branch : Expr<'T -> 'Union>) (inputs : 'Union list) =
    let rec getUnionCase (e : Expr) =
        match e with
        | NewUnionCase(unionCaseInfo,_) -> unionCaseInfo
        | Lambda(_, body) -> getUnionCase body
        | Let(_, TupleGet _, body) -> getUnionCase body
        | _ -> invalidArg "branch" "not a union case constructor"

    let getBranchContents (uci : UnionCaseInfo) (u : 'Union) =
        let uci', fields = FSharpValue.GetUnionFields(u, typeof<'Union>)
        if uci = uci' then
            match fields with
            | [| field |] -> field :?> 'T
            | _ -> FSharpValue.MakeTuple(fields, typeof<'T>) :?> 'T
            |> Some
        else None

    let uci = getUnionCase branch
    inputs |> List.choose (getBranchContents uci)


filterUnionCases <@ Case1 @> [ Case1 "string1" ; Case2 2 ; Case1 "string2" ] //  [ "string1" ; "string2" ]
filterUnionCases <@ Case2 @> [ Case1 "string1" ; Case2 2 ; Case1 "string2" ] //  [ 2 ]

This should work even in union cases that contain multiple fields.

eirik
  • 608
  • 3
  • 11
  • Thanks. I can follow what's going on, but it sure stretches what I'm using now. Lot's to learn – Remko Sep 05 '13 at 07:27
  • You should keep in mind that this is *slow*, since it uses reflection. If performance is important to you, you should go for the solutions offered by the other posters. – eirik Sep 05 '13 at 09:19
  • I will, I circumvented my problem by using a DU of sequences. Thanks again – Remko Sep 06 '13 at 11:51