Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What evidence is there that Clojure Zippers would benefit from being expressed as comonads?

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!

like image 662
hawkeye Avatar asked Aug 19 '14 05:08

hawkeye


1 Answers

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 "extending" 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 extending 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 Monads. And Zipper/Store/Trace would be comonads even if they weren't Comonads.

like image 144
J. Abrahamson Avatar answered Nov 15 '22 07:11

J. Abrahamson