This question is kind of the next level of F# Set using custom class -- I want to define IComparable for a generic interface.
I have an arbitrary set of types which implement a shared metadata exchange interface, ITree
. I want to compare across these types, using only the exposed data in ITree
.
I realize this stuff is not exactly idiomatic F#, but I m trying to interop with existing C# and VB code, so I want to use .NET interfaces and comparison where 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
Now the actual problem: this does not compile because ITree
doesn t yet implement IComparable
. I need a custom comparison that helps with clock skew and eventually other things.
Is there a way I can define the comparison function on ITree
directly so that all the other assemblies don t need to think about it, and can just provide their data?
If I try to do
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
The compiler thinks ITree
is no longer an abstract interface, or something confusing.
Now, I could create a base type that all of the implementors must share, but I don t want to do that because those other types really just need to expose their data on this interface, they already exist, and may already have a base class for some other reason.
Possibly I can use IComparer<T>
, like
type ITreeComparer =
interface IComparer<ITree> with
member x.Compare(this, that) = ...
But then I have no idea how to tell the Set...
functions to use that IComparer.
(I assume that once I figure out how to apply IComparer<T>
, the same methods will work for IEqualityComparer<T>
as needed.)
Edit: I can do
let x = new HashSet<ITree>(a |> Seq.cast<ITree>, new ITreeEqualityComparer())
To use the normal .NET collections, which should be good enough for this problem; however, I would still like to know if there s a better way to do what I m trying to do.