Играя с типами F# и заблудиться
Я немного читал F# и решил попробовать. Я начал с довольно сложного примера, придумал и сразу заблудился. Интересно, кто-то может поделиться некоторыми мыслями по этому поводу
Я хотел написать метод с именем ComparisonStrategy<'T>
который возвращает экземпляр IEqualityComparer<'T>
, Это принимает переменную длину ComparisonWhichAndHow<'T>
экземпляров. Тип ComparisonWhichAndHow<'T>
может быть:
- Одна функция типа
('T -> *)
, который является методом, который выбирает одно поле для сравнения - 2 кортежа
('T -> 'U, IEqualityComparer<'U>)
если вы не хотите по умолчаниюEquals
или жеGetHashCode
использоваться на'U
,
Я уже некоторое время пытался нарисовать это в Visual Studio, но я не могу даже сделать правильную часть объявления функции. Я несколько уверен, что смогу реализовать тело метода, если смогу просто пройти через это, но кажется, что не могу.
Отредактировано:
Это код, который я пробовал до сих пор.
Я пытаюсь достичь 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# для реализации всех функций.