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

Answers


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.


Need Your Help

How to remove sun's reflection on a photo using image processing

python image-processing

I have multiple grayscale images in which each image has the sun's reflection or also known as glare as a bright glaring spot. It looks like a bright white blob which I want to remove. It is basica...

Copying User Profile to Default Profile in Windows 7

windows windows-7 batch-file profile robocopy

I'm trying to find a way using, hopefully, a batch file to copy a modified user profile to the default user profile so that any user that logs in to the computer will adopt the profile setup. We're...