5

我有以下内容:

type union1 =
    | Case1 of string
    | Case2 of int

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

如何更改union1s为 type 序列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 的匹配项中需要类型选项字符串

我已经通过使用 DU 序列解决了我的特定用例。

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

3 回答 3

8

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

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

let case2s = Seq.map matchCase union1s

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

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

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

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

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

于 2013-09-04T07:43:27.070 回答
3

作为备选:

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

let case2s = union1s |> Seq.choose matchCaseOpt

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

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

于 2013-09-04T07:49:44.713 回答
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 ]

即使在包含多个字段的联合情况下,这也应该有效。

于 2013-09-04T13:31:44.730 回答