Instances of both Settable
from lens and Identical
from lens-family must be isomorphic to Identity
. (On why that must be so, see hao's answer to Isn't it redundant for Control.Lens.Setter to wrap types in functors? , and in particular the caveat at the very end of it.) Now, if our goal were merely to make sure a functor is isomorphic to Identity
, we wouldn't necessarily have to set up a brand new class just for that purpose, as the following constraint would suffice:
type Setter s t a b = forall f. (Distributive f, Lone f) => (a -> f b) -> s -> f t
Here, Lone
is a subclass of Traversable
for functors which hold exactly one value, which doesn't rely on Applicative
:
class Traversable f => Lone f where
sequenceL :: Functor m => f (m a) -> m (f a)
traverseL :: Functor m => (a -> m b) -> f a -> m (f b)
Distributive
functors are isomorphic to functions, and Lone
functors are isomorphic to pairs. If a functor is both Distributive
and Lone
, it must be isomorphic to a function functor and to a pair functor. That is only possible if the argument type of the function and the fixed-type component of the pair are both (isomorphic to) ()
, which in turn means the functor must be isomorphic to Identity
.
Arranging the constraint for Setter
in this way wouldn't have been alien to the style of optics type synonyms seen in lens. In fact, it would be reminiscent of how Getter
relies on the combination of Functor
and Contravariant
to ensure the functor it uses is phantom on its argument. One inconvenience, however, is there is no canonical implementation of Lone
in the ecosystem. Since lens would have to define the class anyway, it might as well set up a special purpose class like Settable
instead, which in any case has the advantage of making signatures and type errors slightly more self-explanatory.
Given that Settable
is ultimately a stand-in for the combination of Distributive
and Lone
, it makes sense to make it a subclass of Distributive
and Traversable
. Doing so delineates the provenance of the class, with Lone
being just one further restriction away from Traversable
. While lens-family-core omits the Distributive
constraint (presumably to avoid depending on the distributive package), we can sense a similar motivation from a comment to the definition of Identical
:
-- It would really be much better if comonads was in tranformers
class (Traversable f, Applicative f) => Identical f where
extract :: f a -> a
Lone
functors are all comonads, and adding a Comonad
superclass to Identical
in principle would allow not defining extract
anew, while highlighting a different aspect of what an Identical
functor is supposed to be like.
A couple notes to wrap things up. Firstly, you may have noticed I haven't mention the Applicative
constraint at all so far. Even though the constraint is necessary in practice to make pure
available for the implementation of setting
, it is in a sense not as essential as the others. Distributive functors are necessarily applicative, and Distributive
doesn't have Applicative
as a superclass merely because (<*>)
is awkward to express in terms of the distributive interface. pure
itself, on the other hand, can be straightforwardly implemented in its terms:
pureD :: Distributive g => a -> g a
pureD = cotraverse getConst . Const
Secondly, it is worth mentioning there is a more succinct way to specify that a Functor
(that is, a Hask endofunctor) must be isomorphic to Identity
: using the Adjunction
class to state it must be adjoint to itself. Setter
, then, might be made to look like this:
type Setter s t a b = forall f. Adjunction f f => (a -> f b) -> s -> f t
(Incidentally, Distributive
functors are precisely the right adjoint endofunctors, and Lone
functors are precisely the left adjoint ones.)
This constraint, however, is far from self-explanatory, and Adjunction
isn't exactly a class in widespread use, so ditching Settable
in favour of it doesn't look like a very attractive option.