2009-11-23 2 views
1

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.

Répondre

3

Vous devrez créer un type de wrapper si vous souhaitez stocker les données dans un ensemble F # (Set n'est pas conçu pour fonctionner avec des IComparers personnalisés). Donc, vous pourriez faire, par exemple.

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

puis de stocker ces objets wrapper dans l'ensemble.

+1

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! –

+0

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 ... –

Questions connexes