Cette question est un peu le niveau suivant de F# Set using custom class - Je veux définir IComparable pour une interface générique.Comment définir de manière centralisée IComparable sur les types abstraits (interface) dans F #
J'ai un ensemble arbitraire de types qui implémentent une interface d'échange de métadonnées partagée, ITree
. Je veux comparer entre ces types, en utilisant uniquement les données exposées dans ITree
. Je réalise que ce truc n'est pas exactement F # idiomatique, mais j'essaie d'interagir avec le code C# et VB existant, donc je veux utiliser les interfaces .NET et la comparaison si possible.
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
Maintenant, le problème réel: cela ne compile pas parce que ITree
n'implémente pas encore IComparable
. J'ai besoin d'une comparaison personnalisée qui aide avec l'asymétrie de l'horloge et éventuellement d'autres choses.
Y at-il une manière que je peux définir la fonction de comparaison sur ITree
directement de sorte que tous les autres assemblées ne ont pas besoin d'y penser, et peut tout simplement fournir leurs données?
Si je tente de faire
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
Le compilateur pense ITree
n'est plus une interface abstraite, ou quelque chose de confusion.
Maintenant, je peux créer un type de base que tous les implémenteurs doivent partager, mais je ne veux pas le faire car ces autres types ont juste besoin d'exposer leurs données sur cette interface, ils existent déjà, et peuvent ont déjà une classe de base pour une autre raison.
Peut-être que je peux utiliser IComparer<T>
, comme
type ITreeComparer =
interface IComparer<ITree> with
member x.Compare(this, that) = ...
Mais alors je ne sais pas comment dire aux fonctions Set...
d'utiliser cette IComparer.
(je suppose que, une fois que je figure sur la façon d'appliquer IComparer<T>
, les mêmes méthodes fonctionnent pour IEqualityComparer<T>
au besoin.)
Edit: je peux faire
let x = new HashSet<ITree>(a |> Seq.cast<ITree>, new ITreeEqualityComparer())
Pour utiliser les collections normales de .NET, qui devraient être assez bonnes pour ce problème; Cependant, j'aimerais quand même savoir s'il y a une meilleure façon de faire ce que j'essaie de faire.
Nice, c'est ce que je dois faire alors. Je viens de commencer à essayer de travailler avec HashSet, et l'homme, les signatures sont effrayantes ... UnionWith (seq ): unité - auuuugh! modification sur place! –
Cela fonctionne parfaitement. Pas de duplication de code moche.Maintenant, je souhaite seulement que j'étais assez intelligent pour penser à tout cela sur mon solitaire ... –