That’s an interesting question indeed.
Personally, I like to think of data structures in terms of their “morally ideal forms”, and for Set
, that is something like “zero or one instances of each distinct element value; order is not preserved”. Structure-preserving, then, would mean that for each input element, the output set contains exactly one output element. So if the input set has 6 elements, the output set should also have 6 elements - they can be in any order, but there must be 6 of them, and each must correspond to one of the 6 input elements. That, at least, is my intuition for what fmap
on a Set
should do.
But this is only the case if the fmapped function is injective, otherwise we two different input elements might map to the same output element, so the output set would be smaller than the input set, and “structure” would be lost.
Interestingly, though, the usual functor laws do not forbid this - fmap id === id
and fmap (g . f) === fmap f . fmap g
still hold - but for sets, this isn’t enough to comply with the intuition of “preserving structure” - we can, in fact, do something like fmap (const ())
on any set, and we’ll get a singleton set containing just the unit value for any non-empty input set, regardless of what’s in it, which I would hardly say is “structure preserving” in any useful sense.
Then again, maybe other people have different intuitions about what “structure preserving” is supposed to mean, in which case the Functor
instance would be fine.