data CouldBe a = Is a | Lost deriving (Show, Ord)
instance Eq (CouldBe m) where
Is x == Is y = x == y
Lost == Lost = True
_ == _ = False
Gives an error: No instance for (Eq m) arising from a use of ‘==’
So:
instance (Eq m) => Eq (CouldBe m) where
Is x == Is y = x == y
Lost == Lost = True
_ == _ = False
Works fine (at least I start understanding the errors), but why do I need that constraint? I am trying to learn, so the 'why' is very important to me.
What's a typeclass in Haskell? A typeclass defines a set of methods that is shared across multiple types. For a type to belong to a typeclass, it needs to implement the methods of that typeclass. These implementations are ad-hoc: methods can have different implementations for different types.
In a data declaration, a type constructor is the thing on the left hand side of the equals sign. The data constructor(s) are the things on the right hand side of the equals sign. You use type constructors where a type is expected, and you use data constructors where a value is expected.
An instance of a class is an individual object which belongs to that class. In Haskell, the class system is (roughly speaking) a way to group similar types. (This is the reason we call them "type classes"). An instance of a class is an individual type which belongs to that class.
If you are using an interactive Haskell prompt (like GHCi) you can type :t <expression> and that will give you the type of an expression. e.g. or e.g.
Your original definition said that CouldBe m
was an instance of Eq
for any type m
, even one that doesn't have an Eq
instance. But if that's true, you have to find some way of defining Is x == Is y
without using x == y
(since you haven't required m
to have an Eq
instance, x == y
isn't necessarily defined.)
As a specific example, it prevents you from writing something like
Is (+3) == Is (* 5) -- (+3) == (*5) is undefined
Adding the constraint ensures you can compare two CouldBe
values only if the wrapped type can also be compared.
A "valid", but trivial, instance without adding the constraint:
instance Eq (CouldBe m) where
Is x == Is y = True
Lost == Lost = True
_ == _ = False
Two CouldBe m
values are equal as long as they share the same data constructor, regardless of the wrapped value. No attempt is made to use x
or y
at all, so their types can be unconstrained.
"Valid" is in quotes because this definition could be in violation of the substitutivity law, defined at http://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Eq.html. Suppose you had a function that could pull apart a CouldBe
value:
couldbe :: b -> (a -> b) -> CouldBe a -> b
couldBe x _ Lost = x
couldBe _ f (Is x) = f x
The violation occurs because Is 3 == Is 5
would be true, but let f = couldbe 0 id
. Then f (Is 3) == f (Is 5)
evaluates to 3 == 5
which is false.
Whether it's actually a violation or not depends on the existence of a function like couldbe
that can see "inside" a CouldBe
value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With