Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# container that implements equality only when underlying type does

Tags:

f#

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
like image 655
Joe Huha Avatar asked Feb 13 '23 13:02

Joe Huha


1 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.

like image 153
Jack P. Avatar answered Feb 16 '23 03:02

Jack P.