1

I came across a situation where I'd like compare a discriminated union that has one case which isn't comparable directly and that I have no need to use in (custom) comparison. For that reason, I wrote CustomEquality and CustomComparison functions. Alas I get a stackoverflow and in general I'm uncertain how to handle this kind of a equality override situation in F#. I read Equality and Comparison Constraints in F# by Don Syme, Chris Smith's F# Language Details (Gotchas).

<edit: As pointed out in the comments and as for context, what should be done with the Funky case? I was entertaining a similar idea to How do I create a job queue using a MailboxProcessor? wherein I'd queue DUs that could be used to perform functions or to control a state machine.

Looking at that other post (I refrain from producing the code here), which uses elegantly MailboxProcessor I see it has a similar problem and somewhat similar idea. I'm not sure if the design could be better or is there a way to compare functions and in any event I have some more questions regarding this (with a C# background).

Questions:

  • How to avoid the stackoverflow in the Equals? (Probably something simple, but...)
  • Should the generic IComparable implemented?
  • Should static member (=) etc. be overloaded as per Chris' blog post?
  • What else should be overloaded in DUs in similar situation? Should IEquatable be implemented in case there are performance implementations?

I think some of these could be answered by looking at the IL (e.g. if the compiler already generates IEquatable for DUs), but just to make sure I haven't overlooked anything.

The code

[<CustomEquality; CustomComparison>]
type FunkyUnion =
    | Case1
    | Case2
    | Funky of (unit -> unit)

    override x.Equals(obj) =
        //This here will cause a stackoverflow to occur...
        match obj with
        |  :? FunkyUnion as y -> (x = y)
        |  _ -> false

    override x.GetHashCode() =
        match x with
        | Case1 -> 1
        | Case2 -> 2
        | _     -> 3

    interface System.IComparable with
       member x.CompareTo yobj = 
          match yobj with 
          | :? FunkyUnion as y -> compare x y
          | _ -> invalidArg "yobj" "cannot compare value of different types"

    interface System.IComparable<FunkyUnion> with
        member x.CompareTo(y) =
            compare x y

[<EntryPoint>]
let main argv = 

    let funky1 = FunkyUnion.Case1
    let funky2 = FunkyUnion.Case2

    if funky1 = funky2 then
        printfn "!!!"

    printfn "!"

    0
Community
  • 1
  • 1
Veksi
  • 3,556
  • 3
  • 30
  • 69
  • How do you want to equate `Funky` values? Are you looking for same reference? Or do you want to say to two `Funky` values can never be equal to one another? – Christopher Stevenson Oct 25 '14 at 19:00
  • The idea is to avoid comparing funky values, I'll update the question. – Veksi Oct 25 '14 at 19:11
  • Well, what happens then when two funky values are compared? Perhaps there should be no funky values on the type in the first place? – scrwtp Oct 25 '14 at 19:15
  • Excellent point! Maybe all funky functions should always compare to equal and the result would be the same. This isn't some well thought "production code", but I just a moment ago found a code looks similar in spirit to what I'm striving to at [How do I create a job queue using a MailboxProcessor?](http://stackoverflow.com/questions/1041195/how-do-i-create-a-job-queue-using-a-mailboxprocessor). Let's see if I can provide more context to the question. As an aside, that ``MailboxProcessor`` is an interesting idea... – Veksi Oct 25 '14 at 19:22

1 Answers1

3

You're getting a stack overflow in Equals, because you're using = on the same type you're implementing Equals for. So Equals calls =, = calls Equals, and so on...

What you want to do for Equals is this:

override this.Equals(o) =
    match o with
    | :? FunkyUnion as fu ->
        match this, fu with
        | Case1, Case1 | Case2, Case2 -> true
        | Funky f1, Funky f2 -> (* whatever you want to do for this case *)
        | _, _ -> false
    | _ -> false

You can update CompareTo in a similar way. This should get you started.

scrwtp
  • 13,437
  • 2
  • 26
  • 30
  • I'm editing... But yes, I got that much -- just phrased bit poorly. :) I was trying to come up something simple, simplish, to match the case. I'll update the question a bit and see if this gets me started. – Veksi Oct 25 '14 at 19:26
  • I think this answers the main question. I need to do some research on the minor points. Cheers! – Veksi Oct 25 '14 at 20:46