玩F#类型并迷路

时间:2012-10-21 19:49:53

标签: f# c#-to-f# f#-3.0 f#-scripting

我一直在读F#,并决定尝试一下。我从一个有点参与的例子开始,我想出来并立刻迷路了。我想知道是否有人可以分享一些想法。

我想编写一个名为ComparisonStrategy<'T>的方法,它返回IEqualityComparer<'T>的实例。它采用可变长度的ComparisonWhichAndHow<'T>个实例。类型ComparisonWhichAndHow<'T>可以是:

  1. 类型('T -> *)的一个函数,它是一个选择要比较的单个字段的方法
  2. 如果您不希望在('T -> 'U, IEqualityComparer<'U>)上使用默认EqualsGetHashCode,则
  3. 'U的2元组。
  4. 我试图在视觉工作室上画一段时间,但是我甚至无法将功能声明部分弄清楚。我有点肯定我能够实现方法体,如果我可以通过它,但似乎我不能。

    编辑:

    这是我到目前为止尝试的代码。

    我正在尝试实现以下两项内容。

    1. 想出一种为每个对象生成相等方法的通用方法。
    2. 有时某些业务操作可能需要比较2个对象的某些字段以及其子字段的某些字段。不完全比较。我正在努力使这些代码更简洁,更简单
    3. 这是我到目前为止所做的:

      module Failed =
          open System.Collections.Generic
          open System
      
          type ComparsionOption<'T, 'U> =
              | Compare of ('T -> 'U)
              | CompareWith of ('T -> 'U) * IEqualityComparer<'U>
      
          // TO USE: [<ParamArray>] 
          // TODO: this method returns a dummy for now
          let CompareStrategy (opts : ComparsionOption<'T, _> array) =
              EqualityComparer<'T>.Default
      
          // How it's used
      
          type Person(name : string, id : Guid) = 
              member this.Name = name
              member this.ID = id
      
          let fullCompare : EqualityComparer<Person> =
              CompareStrategy [|Compare(fun (p : Person) -> p.Name);
                                  CompareWith((fun (p : Person) -> p.ID), EqualityComparer<Guid>.Default)|] // error here
      

1 个答案:

答案 0 :(得分:1)

从另一个角度看问题,看起来您希望能够构建以两种不同方式(您指定的方式)执行比较的对象,然后组合它们。

让我们首先看一下构建执行比较的对象的两种方法。您可以IEqualityComparer<'T>代表两者。第一个采用函数'T -> Something并对结果进行比较。您可以定义如下函数:

/// Creates a comparer for 'T values based on a predicate that 
/// selects some value 'U from any 'T value (e.g. a field)
let standardComparer (f:'T -> 'U) = 
  { new IEqualityComparer<'T> with
      member x.Equals(a, b) = 
        (f a).Equals(b)  // Call 'f' on the value & test equality of results
      member x.GetHashCode(a) = 
        (f a).GetHashCode() } // Call 'f' and get hash code of the result

使用F#泛型函数为'T -> 'U,因此您可以投影任何类型的字段(类型必须具有可比性)。第二个原始函数也需要'T -> 'U,但它也需要'U值的比较器,而不是使用默认值:

/// Creates a comparer for 'T values based on a predicate & comparer
let equalityComparer (f:'T -> 'U) (comparer:IEqualityComparer<'U>) = 
  { new IEqualityComparer<'T> with
      member x.Equals(a, b) = 
        comparer.Equals(f a, f b) // Project values using 'f' and use 'comparer'
      member x.GetHashCode(a) =
        comparer.GetHashCode(f a) } // Similar - use 'f' and 'comparer'

现在,您要说的是,您希望采用上述两种方法之一创建的一系列值来构建单一的比较策略。我不完全确定你的意思。当 all 指定的比较器将它们报告为相等时,您是否希望两个对象相等?

假设是这种情况,您可以编写一个组合两个IEqualityComparer<'T>值的函数,并在两个比较器将它们报告为相等时将它们报告为相等:

/// Creates a new IEqualityComparer that is based on two other comparers
/// Two objects are equal if they are equal using both comparers.
let combineComparers (comp1:IEqualityComparer<'T>) (comp2:IEqualityComparer<'T>) =
  { new IEqualityComparer<'T> with
      member x.Equals(a, b) =
        comp1.Equals(a, b) && comp2.Equals(a, b) // Combine results using &&
      member x.GetHashCode(a) =
        // Get hash code of a tuple composed by two hash codes
        hash (comp1.GetHashCode(a), comp2.GetHashCode(a)) }

这基本上是实现了您需要的所有功能。如果你有一些对象Person,你可以像这样构造比较器:

// Create a list of primitive comparers that compare 
// Name, Age and ID using special 'idComparer'
let comparers =
  [ standardComparer (fun (p:Person) -> p.Name);
    standardComparer (fun (p:Person) -> p.Age);
    equalityComparer (fun (p:Person) -> p.ID) idComparer ]

// Create a single comparer that combines all of them...
let comparePerson = comparers |> Seq.reduce combineComparers

您可以使用重载方法等将其包装在面向对象的界面中,但我认为上面的示例显示了解决方案中您需要的所有重要组件。

BTW:在这个例子中,我使用F# object expressions来实现所有功能。