views:

279

answers:

3

I'm trying to use Set operations with a class that I have. Every instance of this class has a unique ID. Do I need to implement the System.IComparable interface and if so how would I?

type SomeClass(id : int) =
    member this.ID = id

let someSet = Set.of_list [SomeClass(1); SomeClass(2)]
let test = someSet.Contains(SomeClass(2))
A: 

I believe you will need to implement IComparer<T> for set comprehensions (e.g. Set.of_list) to work. (Not IComparable<T>, which tends to be less widely used - though I may be wrong.)

This blog post explains in general how to implement interfaces in F#. It also includes a specific example of a type implementing the IComparer<T>, which actually isn't straightforward as you might hope.

type Comp() =  
    interface IComparer with  
        member x.Compare(a, b) = 0  
    member x.Compare(a, b) = (x :> IComparer).Compare(a,b)

Let me know if the works for you. I have some suspicion that you might in fact need to implement the IEqualityComparer<T> instead, since that's what LINQ set extension methods are based around, as far as I know. (It really gets confusing with all these interfaces for comparing in the BCL!)

Noldorin
+2  A: 

Here's an implementation that should work:

type SomeClass(id : int) =    
    member this.ID = id
    override this.Equals(o) =
        match o with
        | :? SomeClass as sc -> this.ID = sc.ID
        | _ -> false
    override this.GetHashCode() =
        id.GetHashCode()
    interface System.IComparable with
        member this.CompareTo(o) =
            match o with
            | :? SomeClass as sc -> compare this.ID sc.ID
            | _ -> -1
Brian
Awesome thanks, Is there something in the F# power pack that implements the active record pattern? Would be cool if I could just inherit it. Hmm, maybe I'll add more to this class and use it as such.
bhd739ge
See my other response.
Brian
Have you thought about just using a dictionary instead of a set?
gradbot
@gradbot - probably a HashSet rather than a Dictionary. These mutable .Net collection types remove the need for IComparable, but still need Equals() and GetHashCode().
Brian
+1  A: 

Regarding the comment on my other answer, you could factor this into a reusable base class, but I'm not sure it's really a good idea:

type EqCompBase<'EqKey, 
        'DerivedType when 'DerivedType :> EqCompBase<'EqKey,'DerivedType> >
        (id : 'EqKey) =    
    member this.ID = id
    override this.Equals(o) =
        match o with
        | :? EqCompBase<'EqKey, 'DerivedType> as sc -> this.ID = sc.ID
        | _ -> false
    override this.GetHashCode() =
        id.GetHashCode()
    interface System.IComparable with
        member this.CompareTo(o) =
            match o with
            | :? EqCompBase<'EqKey, 'DerivedType> as sc -> compare this.ID sc.ID
            | _ -> -1

type SomeClass(id : int, otherFieldThatDoesNotMatterForEquality : string) =
    inherit EqCompBase<int, SomeClass>(id)

let someSet = Set.of_list [SomeClass(1,"yadda"); SomeClass(2,"blah")]
let test = someSet.Contains(SomeClass(2,"foo"))
printfn "%A" test  // true
Brian