F# container that implements equality only when underlying type does
EDIT: It appears from the answer and comment added so far that I have not properly explained what I want. Here is an example:
// type not supporting any type of comparison [<NoEquality>] [<NoComparison>] type blah () = member x.huha = 0 // make a map, turns out to work whether x supports equality or not let inline tt x = Map.ofList [1, x] let test () = // maps can be compared for equality if the argument can if (tt 1 = tt 2) then failwithf "strange" // maps can be made no matter if the argument supports equality if (tt (blah ())).Count <> 1 then failwithf "size" // this does not compile if tt (blah ()) = tt (blah ()) then ....
In short, I want my own type to behave just like map above. So it should support equality when the type argument does, and should not when the type argument doesn't. I also want the typechecker to stop me using equality when not supported, seeing as it clearly can do that for builtin types. Thanks again.
Original question: Various built-in F# types support equality if and only if some underlying type does. For example, Map<'k, 'd> will support equality iff 'd does (and this is detected at compile-time). Is it possible to implement this behaviour in user-code? Here is one failed attempt, and a version that compiles fine if the equality is unconditional. Many thanks.
[<NoComparison>] type test_fails<[<EqualityConditionalOn>]'a> (content:'a) = let eq_impl (x:test_fails<'a>) (y:obj) = let y = y :?> test_fails<'a> x.content = y.content member x.content = content override x.Equals (y:obj) = eq_impl x y [<NoComparison>] type test_compiles<'a when 'a : equality> (content:'a) = let eq_impl (x:test_compiles<'a>) (y:obj) = let y = y :?> test_compiles<'a> x.content = y.content member x.content = content override x.Equals (y:obj) = eq_impl x y
You have part of the solution already: using [<EqualityConditionalOn>] on the generic parameter.
The part you are missing: you need to use Unchecked.equals instead of the normal = operator within your map implementation (anywhere you're checking the equality of two 'a values). Unchecked.equals checks at run-time whether the type supports generic equality. If it does, it compares the two instances/values for equality as usual; if not, it falls back to a structural equality check or the type's implementation of the Object.Equals(obj) method.
As Daniel comments, your problem is that eq_impl is using = on x.content and y.content, which implies that they must support equality. Perhaps you want to use Object.ReferenceEquals instead? It will depend on what, exactly, you're trying to do.