The reason why Set
is not a functor is given here. It seems to boil down to the fact that a == b && f a /= f b
is possible. So, why doesn't Haskell have as standard an alternative to Eq, something like
class Eq a => StrongEq a where
(===) :: a -> a -> Bool
(/==) :: a -> a -> Bool
x /== y = not (x === y)
x === y = not (x /== y)
for which instances are supposed to obey the laws
∀a,b,f. not (a === b) || (f a === f b)
∀a. a === a
∀a,b. (a === b) == (b === a)
and maybe some others? Then we could have:
instance StrongEq a => Functor (Set a) where
-- ...
Or am I missing something?
Edit: my problem is not “Why are there types without an Eq
instance?”, like some of you seem to have answered. It's the opposite: “Why are there instances of Eq
that aren't extensionally equal? Why are there too many Eq
instances?”, combined with “If a == b
does imply extensional equality, why is Set
not an instance of Functor
?”.
Also, my instance declaration is rubbish (thanks @n.m.). I should have said:
newtype StrongSet a = StrongSet (Set a)
instance Functor StrongSet where
fmap :: (StrongEq a, StrongEq b) => (a -> b) -> StrongSet a -> StrongSet b
fmap (StrongSet s) = StrongSet (map s)
instance StrongEq a => Functor (Set a) where
This makes sense neither in Haskell nor in the grand mathematical/categorical scheme of things, regardless of what StrongEq
means.
In Haskell, Functor
requires a type constructor of kind * -> *
. The arrow reflects the fact that in category theory, a functor is a kind of mapping. []
and (the hypothetical) Set
are such type constructors. [a]
and Set a
have kind *
and cannot be functors.
In Haskell, it is hard to define Set
such that it can be made into a Functor
because equality cannot be sensibly defined for some types no matter what. You cannot compare two things of type Integer->Integer
, for example.
Let's suppose there is a function
goedel :: Integer -> Integer -> Integer
goedel x y = -- compute the result of a function with
-- Goedel number x, applied to y
Suppose you have a value s :: Set Integer
. What fmap goedel s
should look like? How do you eliminate duplicates?
In your typical set theory equality is magically defined for everything, including functions, so Set
(or Powerset
to be precise) is a functor, no problem with that.
Since I'm not a category theorist, I'll try to write a more concrete/practical explanation (i.e., one I can understand):
The key point is the one that @leftaroundabout made in a comment:
==
is supposed to witness "equivalent by all observable means" (that doesn't necessarily requirea == b
must hold only for identical implementations; but anything you can "officially" do with a and b should again yield equivalent results. SounAlwaysEq
should never be exposed in the first place). If you can't ensure this for some type, you shouldn't give it anEq
instance.
That is, there should be no need for your StrongEq
because that's what Eq
is supposed to be already.
Haskell values are often intended to represent some sort of mathematical or "real-life" value. Many times, this representation is one-to-one. For example, consider the type
data PlatonicSolid = Tetrahedron | Cube |
Octahedron | Dodecahedron | Icosahedron
This type contains exactly one representation of each Platonic solid. We can take advantage of this by adding deriving Eq
to the declaration, and it will produce the correct instance.
In many cases, however, the same abstract value may be represented by more than one Haskell value. For example, the red-black trees Node B (Node R Leaf 1 Leaf) 2 Leaf
and Node B Leaf 1 (Node R Leaf 2 Leaf)
can both represent the set {1,2}. If we added deriving Eq
to our declaration, we would get an instance of Eq
that distinguishes things we want to be considered the same (outside of the implementation of the set operations).
It's important to make sure that types are only made instances of Eq
(and Ord
) when appropriate! It's very tempting to make something an instance of Ord
just so you can stick it in a data structure that requires ordering, but if the ordering is not truly a total ordering of the abstract values, all manner of breakage may ensue. Unless the documentation absolutely guarantees it, for example, a function called sort :: Ord a => [a] -> [a]
may not only be an unstable sort, but may not even produce a list containing all the Haskell values that go into it. sort [Bad 1 "Bob", Bad 1 "James"]
can reasonably produce [Bad 1 "Bob", Bad 1 "James"]
, [Bad 1 "James", Bad 1 "Bob"]
, [Bad 1 "James", Bad 1 "James"]
, or [Bad 1 "Bob", Bad 1 "Bob"]
. All of these are perfectly legitimate. A function that uses unsafePerformIO
in the back room to implement a Las Vegas-style randomized algorithm or to race threads against each other to get an answer from the fastest may even give different results different times, as long as they're ==
to each other.
tl;dr: Making something an instance of Eq
is a way of making a very strong statement to the world; don't make that statement if you don't mean it.
Your second Functor
instance also doesn't make any sense. The biggest reason why Set
can't be a Functor
in Haskell is fmap
can't have constraints. Inventing different notions of equality as StrongEq
doesn't change the fact that you can't write those constraints on fmap
in your Set instance.
fmap
in general shouldn't have the constraints you need. It makes perfect sense to have functors of functions, for example (without it the whole notion of using Applicative to apply functions inside a functor breaks down), and functions can't be members of Eq or your StrongEq in general.
fmap
can't have extra constraints on only some instances, because of code like this:
fmapBoth :: (Functor f, Functor g) => (a -> b, c -> d) -> (f a, g c) -> (f b, g d)
fmapBoth (h, j) (x, y) = (fmap h x, fmap j y)
This code claims to work regardless of the functors f
and g
, and regardless of the functions h
and j
. It has no way of checking whether one of the functors is a special one that has extra constraints on fmap
, nor any way of checking whether one of the functions it's applying would violate those constraints.
Saying that Set is a Functor in Haskell, is saying that there is a (lawful) operation fmap :: (a -> b) -> Set a -> Set b
, with that exact type. That is precisely what Functor
means. fmap :: (Eq a -> Eq b) => (a -> b) -> Set a -> Set b
is not an example of such an operation.
It is possible, I understand, to use the ConstraintKinds GHC extendsion to write a different Functor class that permits constraints on the values which vary by Functor (and what you actually need is an Ord
constraint, not just Eq
). This blog post talks about doing so to make a new Monad class which can have an instance for Set. I've never played around with code like this, so I don't know much more than that the technique exists. It wouldn't help you hand off Sets to existing code that needs Functors, but you should be able to use it instead of Functor in your own code if you wish.
This notion of StrongEq
is tough. In general, equality is a place where computer science becomes significantly more rigorous than typical mathematics in the kind of way which makes things challenging.
In particular, typical mathematics likes to talk about objects as though they exist in a set and can be uniquely identified. Computer programs usually deal with types which are not always computable (as a simple counterexample, tell me what the set corresponding to the type data U = U (U -> U)
is). This means that it may be undecidable as to whether two values are identifiable.
This becomes an enormous topic in dependently typed languages since typechecking requires identifying like types and dependently typed languages may have arbitrary values in their types and thus need a way to project equality.
So, StrongEq
could be defined over a restricted part of Haskell containing only the types which can be decidably compared for equality. We can consider this a category with the arrows as computable functions and then see Set
as an endofunctor from types to the type of sets of values of that type. Unfortunately, these restrictions have taken us far from standard Haskell and make defining StrongEq
or Functor (Set a)
a little less than practical.
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