Играя с типами F# и заблудиться

Я немного читал F# и решил попробовать. Я начал с довольно сложного примера, придумал и сразу заблудился. Интересно, кто-то может поделиться некоторыми мыслями по этому поводу

Я хотел написать метод с именем ComparisonStrategy<'T> который возвращает экземпляр IEqualityComparer<'T>, Это принимает переменную длину ComparisonWhichAndHow<'T> экземпляров. Тип ComparisonWhichAndHow<'T> может быть:

  1. Одна функция типа ('T -> *), который является методом, который выбирает одно поле для сравнения
  2. 2 кортежа ('T -> 'U, IEqualityComparer<'U>) если вы не хотите по умолчанию Equals или же GetHashCode использоваться на 'U,

Я уже некоторое время пытался нарисовать это в Visual Studio, но я не могу даже сделать правильную часть объявления функции. Я несколько уверен, что смогу реализовать тело метода, если смогу просто пройти через это, но кажется, что не могу.

Отредактировано:

Это код, который я пробовал до сих пор.

Я пытаюсь достичь 2 следующих вещей.

  1. Придумайте общий способ создания равного метода для каждого объекта.
  2. Иногда для некоторых бизнес-операций может потребоваться сравнение некоторых полей двух объектов и некоторых полей их дочерних элементов. Не полное сравнение. Я пытаюсь сделать написание этого кода более кратким и простым

Это то, что я до сих пор:

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 ответ

Если посмотреть на проблему с другой точки зрения, похоже, что вы хотите иметь возможность создавать объекты, которые выполняют сравнение двумя различными способами (которые вы указали), а затем составлять их.

Давайте начнем с рассмотрения двух способов создания объекта, который выполняет сравнение. Вы можете представлять как 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

Функция 'T -> 'U с использованием обобщений F#, так что вы можете проецировать поля любого типа (тип должен быть сопоставим). Вторая примитивная функция также принимает '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'

Теперь вы говорите, что вы хотите взять последовательность значений, созданных одним из двух вышеупомянутых способов, для построения единой стратегии сравнения. Я не совсем уверен, что вы подразумеваете под этим. Вы хотите, чтобы два объекта были равны, когда все указанные компараторы сообщают о них как о равных?

Предполагая, что это так, вы можете написать функцию, которая объединяет два 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

Вы можете обернуть это в более объектно-ориентированный интерфейс, используя перегруженные методы и т. Д., Но я думаю, что в приведенном выше примере показаны все важные компоненты, которые вам понадобятся в решении.

Кстати: в этом примере я использовал объектные выражения F# для реализации всех функций.

Другие вопросы по тегам