代码之家  ›  专栏  ›  技术社区  ›  Emile

F:如何优雅地选择和分组有区别的工会?

f#
  •  15
  • Emile  · 技术社区  · 14 年前

    假设我有一个形状列表:

    type shape = 
    | Circle of float
    | Rectangle of float * float
    
    let a = [ Circle 5.0; Rectangle (4.0, 6.0)]
    

    然后如何测试,例如,圆存在于中?我可以为每个形状创建一个函数

    let isCircle s = 
        match s with
        | Circle -> true
        | _ -> false
    List.exists isCircle a
    

    但我觉得在f中必须有一种更优雅的方式,而不是必须为每种形状类型定义这样的函数。有?

    相关问题是如何根据形状类型对形状列表进行分组:

    a |> seq.groupBy( <shapetype? >)
    
    5 回复  |  直到 11 年前
        1
  •  7
  •   desco    14 年前

    您可以将f反射与引用结合起来,得到通用的解决方案

    type Shape = 
        | Circle of float
        | Rectangle of float * float
    
    let isUnionCase (c : Expr<_ -> 'T>)  = 
        match c with
        | Lambdas (_, NewUnionCase(uci, _)) ->
            let tagReader = Microsoft.FSharp.Reflection.FSharpValue.PreComputeUnionTagReader(uci.DeclaringType)
            fun (v : 'T) -> (tagReader v) = uci.Tag
        | _ -> failwith "Invalid expression"
    
    let a = 
        [ Circle 5.0; Rectangle (4.0, 6.0)] 
            |> List.filter (isUnionCase <@ Rectangle @>)
    printf "%A" a
    
        2
  •  16
  •   RD1    14 年前

    如果您对不同类别的形状感兴趣,那么定义另一种类型来准确捕获它们是有意义的:

    type shapeCategory = Circular | Rectangular
    
    let categorize = function
        | Circle _ -> Circular
        | Rectangle _ -> Rectangular
    
    List.exists ((=) Circular) (List.map categorize a)
    
    a |> Seq.groupBy(categorize)
    

    编辑-正如Brian建议的,您可以使用活动模式而不是新类型。对于您的示例,它的工作方式非常类似,但可以更好地扩展到更复杂的模式,而如果您的代码经常与类别一起工作,并且您希望为它们提供一个好的联合类型,而不是选择类型,那么上面的方法可能会更好。

    let (|Circular|Rectangular|) = function 
        | Circle _ -> Circular
        | Rectangle _ -> Rectangular 
    
    List.exists (function Circular -> true | _ -> false) a
    
    let categorize : shape -> Choice<unit, unit> =  (|Circular|Rectangular|) 
    a |> Seq.groupBy(categorize)
    
        3
  •  8
  •   kvb    14 年前

    您可以使用f反射库获取值的标记:

    let getTag (a:'a) = 
      let (uc,_) = Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(a, typeof<'a>)
      uc.Name
    
    a |> Seq.groupBy getTag
    
        4
  •  3
  •   Nikon the Third    11 年前

    我想添加另一个解决方案,它可以根据所提供的DESCO为每个联合用例提供报价。这就是:

    open Microsoft.FSharp.Quotations.Patterns
    open Microsoft.FSharp.Reflection
    
    let rec isUnionCase = function
    | Lambda (_, expr) | Let (_, _, expr) -> isUnionCase expr
    | NewTuple exprs -> 
        let iucs = List.map isUnionCase exprs
        fun value -> List.exists ((|>) value) iucs
    | NewUnionCase (uci, _) ->
        let utr = FSharpValue.PreComputeUnionTagReader uci.DeclaringType
        box >> utr >> (=) uci.Tag
    | _ -> failwith "Expression is no union case."
    

    通过这种方式定义,isonioncase的工作方式与desco显示的类似,但即使在空的联合用例或具有多个值的联合用例上也是如此。还可以输入一个由逗号分隔的并集大小写组成的元组。考虑一下:

    type SomeType =
    | SomeCase1
    | SomeCase2 of int
    | SomeCase3 of int * int
    | SomeCase4 of int * int * int
    | SomeCase5 of int * int * int * int
    
    let list =
        [
            SomeCase1
            SomeCase2  1
            SomeCase3 (2, 3)
            SomeCase4 (4, 5, 6)
            SomeCase5 (7, 8, 9, 10)
        ]
    
    list 
    |> List.filter (isUnionCase <@ SomeCase4 @>)
    |> printfn "Matching SomeCase4: %A"
    
    list
    |> List.filter (isUnionCase <@ SomeCase3, SomeCase4 @>)
    |> printfn "Matching SomeCase3 & SomeCase4: %A"
    

    我提供的第一个独立案例只适用于单个案例检查。后来我添加了对newtuple的表达式检查,并认为您可能会喜欢它。只要确保更改代码,预计算仍然有效,这就是为什么 iucs 在返回的匿名函数之外定义。

        5
  •  0
  •   Ronald Wildenberg    14 年前

    更优雅的解决方案可能是:

    let shapeExistsInList shapeType list =
        List.exists (fun e -> e.GetType() = shapeType) list
    
    let circleExists = shapeExistsInList ((Circle 2.0).GetType()) a
    

    不过,我自己对此并不十分满意,因为你必须创建一个歧视工会的实例,让它发挥作用。

    按形状类型分组可以以类似的方式工作。