如何将一系列有区别的联合(所有项目属于同一案例)映射到案件类型的一系列项目上?

时间:2013-09-04 07:34:22

标签: f#

我有以下内容:

type union1 =
    | Case1 of string
    | Case2 of int

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

如何将union1s更改为seq<int>类型的序列?

类似的东西:

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

let case2s = Seq.map matchCase union1s

此尝试不起作用,因为matchCase无法返回两种不同的类型。

建议的答案有同样的问题(如果我理解正确的话)

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

let case2s = Seq.choose matchCaseOpts unions1s

表达式Some x期望在Case2的匹配中期望类型为Option字符串

我通过使用序列DU来解决我的特定用例。

type Union1s =
    | Case1s of seq<string>
    | Case2s of seq<int>    

3 个答案:

答案 0 :(得分:8)

您假设您的序列不包含单个case1,因此如果不是这样,则需要抛出异常。

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

let case2s = Seq.map matchCase union1s

另一种方法,如果您不确定序列是否包含始终相同的情况,则使用选项

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

然后它取决于你将如何处理这些情况,你可以使用Seq.choose而不是Seq.map过滤出None值,如另一个答案所示。

如果您考虑将Case1s的参数作为例外情况或程序逻辑的一部分,那么遵循哪种方法取决于哪种方法。最近有一个关于F#: Some, None, or Exception?的问题。

如果您没有混合案例,那么使用DU序列是正确的,这样您的DU类型会将您的域限制为实际案例。

答案 1 :(得分:3)

作为替代方案:

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

let case2s = union1s |> Seq.choose matchCaseOpt

此版本将删除除Case2之外的任何情况,如果发生这些情况,Gustavo的解决方案将抛出异常。当然,哪种解决方案最好取决于您的具体要求。

请注意,此解决方案使用Seq.choose而不是Seq.map。

答案 2 :(得分:2)

您可以尝试以下基于反射的通用实现:

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 ]

即使在包含多个字段的联合案例中也是如此。