Consider this class:
class Has record part where extract :: record -> part update :: (part -> part) -> record -> record
It captures the notion of some product type
record having a field of the type
part which can be
extracted from the
record, or the functions on which can be used to
update the whole record (in a lens-ish manner).
What happens if we turn the arrows? Following the types and noting that a sum type is dual to a product type, and a “factor” in a product type is analogous to an option in a sum type, we get
class CoHas sum option where coextract :: option -> sum coupdate :: (sum -> sum) -> option -> option
Firstly, is this line of reasoning correct at all?
If it is, what is the meaning of
coextract produces the
sum out of one of its
options, so it might as well be called
inject or something similar.
coupdate is more interesting. My intuition is that, given a function
f that updates a sum type, it gives us a function that can be used to update one of its options. But, obviously, not every
f is fit for this! Consider
badF :: Either Int Char -> Either Int Char badF (Left n) = n badF (Right _) = Left 0
coupdate badF does not make sense where
coupdate is taken from
CoHas (Either Int Char) Char. One requirement seems to be that the function passed to
coupdate must not change the tags of the sum type.
So here’s the second question: what’s the dual of this requirement in the
My intuition is that it’s not as straightforward because
Has produces a function and
CoHas consumes a function. Things get more symmetric if we consider the rules for the type classes, something along the lines of
update f . update g = update (f . g)
update id = id
extract . update f = f . extract
Now we can actually talk about bad instances of
update functions breaking these rules. But even with this additional constraint, I’m not sure I follow what the laws for the functions that
coupdate accepts should be and how one could derive them from such duality-based reasoning.