如何将有区别的并集序列(其中所有项目都属于相同的大小写)映射到大小写类型的项目序列上

  • 本文关键字:项目 大小写 属于 类型 映射 有区别 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>    

您假设您的序列不包含一个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

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

采用哪种方法取决于您是否将Case1的自变量视为特殊情况或程序逻辑的一部分。有一个关于F#的问题:有的、没有还是例外?不久前

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

作为替代方案:

let matchCaseOpt item =
    match item with
    | Case2 x -> Some(x)
    | _ -> None
let case2s = union1s |> Seq.choose matchCaseOpt

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

请注意,此解决方案使用顺序选择而不是顺序映射。

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

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 ]

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

最新更新