In this presentation [2005] we read at slide 32:
The zipper datatype hides a comonad. This is exactly the comonad one needs to structure attribute evaluation.
So it seems you can express Zippers in terms of Comonads. This even seems possible in Scala.
Looking at the zipper source, we see zippers expressed as Clojure metadata.
My question is, What evidence is there that Clojure Zippers would benefit from being expressed as comonads?
Eric suggests the benefit is
so we need to get all the possible zippers over the original group!
There's a bit of a structural fallacy to what you're asking. It is not that Zippers can be expressed as Comonads, but instead that they are by nature.
Similarly, integers simply are monoids (in two ways!) whether or not you choose to accept the fact.
So, instead of asking what the benefits are what you should ask is "can I improve clarity by recognizing the comonadic structure?"
The answer there is "yes!"
Comonadic structure means that there exist two interesting methods on any zipper. The first is obvious and obviously useful—the "here" function. In order to make this more concrete, I'll make a list zipper
data Zipper a = Zipper { before :: [a], here :: a, after :: [a] }
and now here :: Zipper a -> a
is the comonadic function normally known as extract
.
extract = here
Thus, it might be fair to say that every time you examine the thing a zipper points at, you're using the comonadic interface.
That said, extract
is the boring side of the interface. What's more interesting is extend
.
extend :: (Zipper a -> b) -> Zipper a -> Zipper b
What extend
captures is the idea of applying a "contextualized transformation" to every element in the zipper. Comonadic structure notes that there is a standard and well-structured method of doing this that arises by "extend
ing" the transformation to the whole comonad.
Such an example might be applying a convolution to the list—for instance, a little blurring function:
blurKernel :: Fractional a => Zipper a -> a
blurKernel (Zipper prior current future) =
(a + current + c) / 3
where
a = case prior of
[] -> 0
(p:ps) -> p
c = case future of
[] -> 0
(p:ps) -> p
blur :: Fractional a => Zipper a -> Zipper a
blur = extend blurKernel
So why write blur
in these terms? Is there not a natural, recursive or iterative formulation which would work the same and be more obvious?
Well, by recognizing that blur
is based on a comonadic extension we've exposed common structure in our operations on Zippers. This can be beneficial for maintaining DRY.
We've also begun to recognize something profound about Zippers—every zipper has comonadic extend
so perhaps we can generalize blur
to all Zippers of Fractional
types by somehow generalizing only blurKernel
and extend
ing it in each Zipper we care about.
In any case, I hope my example demonstrates that Zippers are comonads whether you notice it or not.
This is generally the case with good Haskell abstractions—they are natural properties about the way certain kinds of code operate. Type classes merely capture them for convenience's sake. Maybe
/State
/List
/etc would be monads even if they weren't Monad
s. And Zipper
/Store
/Trace
would be comonads even if they weren't Comonad
s.
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