I have been reading up on adjunctions during the last couple of days. While I start to understand their importance from a theoretical point of view, I wonder how and why people use them in Haskell. Data.Functor.Adjunction
provides an implementation and among its instances are free functor / forgetful functor and curry / uncurry. Again those are very interesting from the theoretical view point but I can't see how I would use them for more practical programming problems.
Are there examples of programming problems people solved using Data.Functor.Adjunction
and why you would prefer this implementation over others?
Preliminary note: This answer is a bit speculative. Much like the question, it was built from studying
Data.Functor.Adjunction
.I can think of three reasons why there aren't many use cases for the
Adjunction
class in the wild.Firstly, all Hask/Hask adjunctions are ultimately some variation on the currying adjunction, so the spectrum of potential instances isn't all that large to begin with. Many of the adjunctions one might be interested on aren't Hask/Hask.
Secondly, while an
Adjunction
instance gives you a frankly awesome amount of other instances for free, in many cases those instances already exist somewhere else. To pick the ur-example, we might very easily implementStateT
in terms ofControl.Monad.Trans.Adjoint
:However, no one needs to actually do that, because there is a perfectly good
StateT
in transformers. That said, if you do have anAdjunction
instance of your own, you might be in luck. One little thing I have thought of that might make sense (even if I haven't actually seen it out there) are the following functors:We might write an
Adjunction ChoiceF Dilemma
instance, which reflects howDilemma (ChoiceF a)
is materialised version ofState Bool a
.Dilemma (ChoiceF a)
can be thought of as a step in a decision tree: choosing one side of theDilemma
tells you, through theChoiceF
constructors, what choice is to be made next. TheAdjunction
instance would then give us a monad transformer forDilemma (ChoiceF a)
for free.(Another possibility might be exploiting the
Free f
/Cofree u
adjunction.Cofree Dilemma a
is an infinite tree of outcomes, whileFree ChoiceF a
is a path leading to an outcome. I hazard there is some mileage to get out of that.)Thirdly, while there are many useful functions for right adjoints in
Data.Functor.Adjunction
, most of the functionality they provide is also available throughRepresentable
and/orDistributive
, so most places where they might be used end up sticking with the superclasses instead.Data.Functor.Adjunction
, of course, also offers useful functions for left adjoints. On the one hand, left adjoints (which are isomorphic to pairs i.e. containers that hold a single element) are probably less versatile than right adjoints (which are isomorphic to functions i.e. functors with a single shape); on the other hand, there doesn't seem to be any canonical class for left adjoints (not yet, at least), so that might lead to opportunities for actually usingData.Functor.Adjunction
functions. Incidentally, Chris Penner's battleship example you suggested arguably fits the bill, as it does rely on the left adjoint and how it can be used to encode the representation of the right adjoint:CoordF
, the left adjoint, carries coordinates for the board and a payload.zapWithAdjunction
makes it possible to (quite literally, in this case), target the position while using the payload.