Как централизовать определение IComparable в абстрактных (интерфейсных) типах в F #

Этот вопрос является видом следующего уровня F # Установить с использованием пользовательского класса - Я хочу определить IComparable для общего интерфейса.

У меня есть произвольный набор типов, которые реализуют общий интерфейс обмена метаданными, ITree. Я хочу сравнить эти типы, используя только открытые данные в ITree.

Я понимаю, что этот материал не совсем идиоматический F #, но я пытаюсь взаимодействовать с существующими С# и VB-кодом, поэтому я хочу, когда это возможно, использовать интерфейсы .NET и сравнение.

open System
open System.Collections.Generic

// Simplified "generic" metadata type that all implementers agree on
type ITree =
  abstract Path: string with get, set
  abstract ModifyDate: DateTime with get, set

type Thing1(path, date) =
  interface ITree with
    member x.Path = path
    member x.ModifyDate = date

// In reality, the types implementing ITree are going to
// come from different external assemblies
type Thing2(path, date) =
  interface ITree with
    member x.Path = path
    member x.ModifyDate = date

let d1 = DateTime.Now
let d2 = DateTime.Now.AddMinutes(-2.0)
let xs : seq<ITree> = Seq.cast [ Thing1("/stuff", d1); Thing1("/dupe", d1); Thing1("/dupe", d1) ]
let ys : seq<ITree> = Seq.cast [ Thing2("/stuff", d2); Thing2("/dupe", d1) ]

// Then I would like to take advantage of F# Sets
// to do comparison across these things
let xset = Set.ofSeq xs
let yset = Set.ofSeq ys

let same = Set.intersect xset yset
let diffs = (xset + yset) - same

Теперь актуальная проблема: это не скомпилируется, потому что ITree еще не реализует IComparable. Мне нужно индивидуальное сравнение, которое помогает с перекосом часов и, в конечном итоге, другими вещами.

Есть ли способ определить функцию сравнения на ITree напрямую, чтобы все остальные сборки не нуждались в этом думать и могли просто предоставить свои данные?

Если я попытаюсь сделать

type ITree =
  abstract Path: string with get, set
  abstract ModifyDate: DateTime with get, set
    interface IComparable<ITree> with
      let Subtract (this: ITree) (that: ITree) =
        this.ModifyDate.Subtract(that.ModifyDate)
      match compare (this.Path, this.ParentPath) (that.Path, this.ParentPath) with
      | 0 ->
        // Paths are identical, so now for a stupid timespan comparison
        match abs (Subtract this that).TotalSeconds with
        | x when x > 60.0  -> int x
        | _ -> 0
      | x -> x

Компилятор считает, что ITree больше не является абстрактным интерфейсом или чем-то запутанным.

Теперь я мог бы создать базовый тип, который должны использовать все разработчики, но я не хочу этого делать, потому что эти другие типы действительно просто должны раскрывать свои данные на этом интерфейсе, они уже существуют и могут уже имеют базовый класс по какой-то другой причине.

Возможно, я могу использовать IComparer<T>, например

type ITreeComparer =      
  interface IComparer<ITree> with
    member x.Compare(this, that) = ...

Но потом я понятия не имею, как сообщить функциям Set... использовать этот IComparer.

(Я предполагаю, что как только я выясню, как применить IComparer<T>, те же методы будут работать для IEqualityComparer<T> по мере необходимости.)


Изменить: Я могу сделать

let x = new HashSet<ITree>(a |> Seq.cast<ITree>, new ITreeEqualityComparer())

Использовать обычные .NET-коллекции, которые должны быть достаточно хороши для этой проблемы; однако, я все равно хотел бы знать, есть ли лучший способ сделать то, что я пытаюсь сделать.

Ответ 1

Вам понадобится создать тип оболочки, если вы хотите сохранить данные в наборе F # (Set не предназначен для работы с пользовательскими IComparers). Таким образом, вы можете сделать, например,

type TreeWithComparer(tree:ITree) =
    member this.Data = tree
    interface IComparable with ...
        // define the custom logic you need

а затем сохраните эти объекты-обертки в Set.