为区分联合类型实现快速CustomEquality和CustomComparison

时间:2012-03-01 14:42:13

标签: hash f# pattern-matching equality discriminated-union

为了用键引用一些坐标,我想使用有区别的联合类型,因为它们允许所有种类的有效模式匹配。

考虑下面的代码snipet:

[<CustomEquality; CustomComparison>]
type Coord = 
| Spot of AssetKey
| Vol of AssetKey * DateTime option 
| Rate of Currency                                   
.....

    member this.sortKey = 
        match this with
        | Spot(key)                               -> (0 , key.toString)
        | Vol(key)                                -> (1 , key.toString)
        | Vol(key, Some(t))                       -> (2 , key.toString + t.ToShortString())
        | Rate(cur)                               -> (3 , cur.toString)
        ......

    interface IComparable with 
        member this.CompareTo(obj) = 
            match obj with 
            | :? Coord as other -> compare this.sortKey other.sortKey
            | _ -> invalidArg "obj" "not a Coord type"

    override this.Equals(obj) = 
            match obj with 
            | :? Coord as other -> this.sortKey = other.sortKey
            | _ -> false

    override this.GetHashCode() = this.sortKey.GetHashCode()

我需要强制执行特定的排序顺序。例如Spot&lt; Vol总是。我用sortKey成员函数强制执行。

AssetKey是一个非常相似的歧视联合类型:

[<StructuralEqualityAttribute; StructuralComparisonAttribute>]
type AssetKey =
| Equity of string
| EquityIndex of string
.....

所以这一切都很好用,但速度很慢。据我所知,如果调用sortKey函数,则会再次构建整个键,特别是再次调用toString函数。

一个明显的改进是添加一个缓存层,这比解决方案更糟糕。

进一步的优化是在字符串上使用散列键。但在这里我还需要添加缓存,因为我需要缓存哈希键,我不想重新计算它。

如果我使用结构或类,性能优化会更容易,但是我失去了模式匹配的灵活性,例如

match c with 
| Coord.Vol(ak, _) when ak = assetKey -> true
| _ -> false

什么是替代方法,表现良好?在我的一些时间中,sortKey函数中丢失了30%甚至更多的整体性能。

感谢您的任何建议和改进。

2 个答案:

答案 0 :(得分:2)

简单优化
您可以轻松完成的一项基本优化是,当您只根据toString的类型做出决定时,可以避免调用Coord。您可以写下:{/ 1>而不是构建sortKey

// Separate functions that return tag and key, so that we don't
// have to call 'toString' if we can decide based just on the Tag
member this.Tag = 
    match this with 
    | Spot _ -> 0 | Vol(_, None) -> 1 
    | Vol _ -> 2 | Rate _ -> 3
member this.Key = 
    match this with 
    | Spot(key) | Vol(key, None) -> key.toString | Rate cur -> cur.toString
    | Vol(key, Some t) -> key.toString + t.ToShortString())  

interface IComparable with  
    member this.CompareTo(obj) =  
        match obj with  
        | :? Coord as other -> 
            let c = compare this.Tag other.Tag
            // Try comparing based on the tag first - if the tags 
            // are the same, then get Key and compare based on the key
            if c <> 0 then c else compare this.Key other.Key
        | _ -> invalidArg "obj" "not a Coord type" 

如果要缓存toString的结果,则需要使用一些允许存储本地字段的结构。我可能会使用一个对象类型(表示为类或简单结构)。

包装类型
在这种情况下,您仍然可以使用活动模式获得良好的模式匹配,但它需要为每个类定义一个活动模式(这可能不是那么糟糕)。这是一个例子:

// This type will not be used directly - it is an internal implementation
// hidden from the users that will be accessed using active patterns
[<RequiresQualifiedAccess>]
type AssetKeyInternal =       
  | Equity of string       
  | EquityIndex of string  
  override x.ToString() = ...

// Public type with active patterns for pattern matching
type AssetKey(key:AssteKeyInternal) = 
  let str = lazy key.ToString() // Lazily cached string
  member x.Key = str.Value      // Evaluated when accessed for the first time

  member x.Value = key // Returns the internal representation

// Define active patterns working over AssetKey type
let (|Equity|EquityIndex|) (k:AssetKey) =
  match k.Value with
  | AssetKeyInternal.Equity(e) -> Equity(e)
  | AssetKeyInternal.EquityIndex(e) -> EquityIndex(e)

给定类型AssetKey的值,您现在可以编写k.Key来获取缓存的字符串表示形式,并且可以使用活动模式对其进行模式匹配:

match k with 
| Equity k -> ...
| EquityIndex i -> ...

答案 1 :(得分:1)

您可以考虑做类似

的事情
type CoordRepr =
| Spot of AssetKey 
| Vol of AssetKey * DateTime option  
| Rate of Currency              

let sortKey = function
| Spot(key) -> 1,key.ToString()
| Vol(key,None) -> 2,key.ToString()
| Vol(key,Some(v)) -> 2,key.ToString() + v.ToShortDateString()
| Rate(key) -> 3,key.ToString()

type Coord(repr) =
    let sortKey = sortKey repr
    member __.Repr = repr
    member __.SortKey = sortKey
    override __.Equals(that) =
        match that with
        | :? Coord as c -> sortKey = c.SortKey
        | _ -> false
    override __.GetHashCode() = sortKey.GetHashCode()
    interface System.IComparable with
        member __.CompareTo(that) =
            match that with
            | :? Coord as c -> compare sortKey c.SortKey
            | _ -> failwith "invalidArg"

let Spot k = Coord(Spot k)
let Vol(k,v) = Coord(Vol(k,v))
let Rate(k) = Coord(Rate(k))

let (|Spot|Vol|Rate|) (c:Coord) =
    match c.Repr with
    | Spot k -> Spot k
    | Vol(k,v) -> Vol(k,v)
    | Rate k -> Rate k

然后使用签名文件隐藏CoordReprCoord的构造函数,sortKey等。