Optional Generic Equality on a Data-Type in F#
Lately I implemented my own immutable Queue and came upon a problem implementing equality for it. The problem goes like this: You want a generic data-type that supports equality. But it only should support equality if the generic type also supports equality.
Seems a little bit weird? But this is actually a common problem in F# that is already solved without that you probably even noticed that this is a problem. At least I did not realize it until I tried to implement a Queue with equality myself.
But to introduce the problem. Let’s talk about List
in F#. Currently you can write this.
|
|
In F# you can compare two lists (Something you cannot do in C# for example).
The above statement will return true
because both lists are equal. But if equality works or not
depends on the type inside the list. In the above example it is int
.
But when you use a type that has now equality like a function.
|
|
Then you get a compile-error
The type '('a -> 'a)' does not support the 'equality' constraint because it is a function type
Or in other words equality for a list depends on the generic type you use. Let’s try to implement
our own List
to see the problem more clear and how to solve this.
Our own List
We start with
|
|
implementing Equality is easy. As a function we could just write.
|
|
And our own List type work as expected. Here is a small test.
|
|
The above code will print Equal
as expected. We also can create a
list that contains functions without a problem.
|
|
but comparing them doesn’t work
|
|
At this moment we only have a function named equal
but maybe we also want to overload equality so we can use
=
in our code. Then things start to get complicated.
Overloading Equality
The first attempt would be
|
|
But the F# compiler will give the following errors for the highlighted lines.
2 -> The signature and implementation are not compatible because the declaration
of the type parameter 'a' requires a constraint of the form 'a: equality
6,14 -> A type parameter is missing a constraint 'when 'a: equality'
We can fix this by adding the constraint.
|
|
But then you lose the ability to use MyList
with a generic types that has no
equality. Because now all types must have equality. So you cannot write.
|
|
The above code now returns the error
The type '('a -> 'a)' does not support the 'equality' constraint because it is a function type
So what we need is either two types one with the generic constraint and one without or the ability to say that equality depends on the generic type we pass. If the generic type has equality then our own List implementation can be compared otherwise not.
Luckily F# already supports this.
EqualityConditionalOn
F# has the attributes EqualityConditionalOn
and there is also a ComparisonConditionalOn
that works the same way. But only adding those will still not solve the problem.
|
|
you still get the same errors. So what do we missing here?
The answer is that we need to replace the =
with a non-generic version as
using =
always expects the equal constraint. So the last piece we need to change is
use Unchecked.equals
(or Unchecked.compare
when you implement comparison).
|
|
Now you have a list that can contain any generic type also those without equality and
on those with equality you also can use =
.
|
|
Summary
Sometimes it is interesting how much pain a simple (whatever…) feature like overloading can cause.
If you want to see a full example with Comparision than you can look at my Immutable Queue implementation.