At work I was recently tasked with figuring out what API calls our program makes, and more interestingly, which code-paths lead to those API calls. Determining this by hand is tedious and error-prone, and worse, doesnât stay up to date with code changes. Instead, letâs see how we can use the type system to eliminate the pain.

The existing code was organized around a class `HasAPI`

that looks something like this:

```
type HasAPI :: Service -> Symbol -> Constraint
class HasAPI srv name where
type APICall srv name
callAPI :: APICall srv name
```

Here, `HasAPI`

is a type class with an associated type family `APICall`

which gives the type for making the call. For example, there might be an instance:

```
instance HasAPI ShoutService "shout" where
type APICall ShoutService "shout" = String -> IO String
= pure $ fmap toUpper str callAPI str
```

This is a silly example â the real codebase makes actual API calls â but it serves for demonstration.

Our goal is to document every codepath that makes any use of `callAPI`

, in some sense, âinfectingâ every path with some marker of that fact. This is a common experience to Haskell programmers; in fact, `IO`

has this same pattern of infectiousness. Whenever you make a function perform IO, every type in the callstack needs to document the fact it performs `IO`

. This is the inspiration we will take, except that changing types is extremely expensive. What if we pushed a constraint around instead?

The trick is to define a new class, of the same shape as `HasAPI`

:

```
type CallsAPI :: Service -> Symbol -> Constraint
class CallsAPI srv name
```

but crucially, we give `CallsAPI`

*no instances.* On first blush, this seems insane: why introduce a class with no methods and no instances? Having no methods means it canât do anything useful. Having no instances means GHC can never eliminate the constraint, and thus must propagate it upwards. This is the infectiousness we want; any function which makes an API call must document that fact in its type â failure to do so will result in GHC failing to compile with the message `No instance for (CallsAPI srv name)`

.

The trick now is to ensure that `callsAPI`

produces a `CallsAPI`

constraint. The easy way to do this is a little renaming to ensure existing polymorphic code continues work:

```
type UnsafeHasAPI :: Service -> Symbol -> Constraint
class UnsafeHasAPI srv name where
type APICall srv name
unsafeCallAPI :: APICall srv name
type HasAPI :: Service -> Symbol -> Constraint
type HasAPI = (UnsafeHasAPI srv name, CallsAPI srv name)
callAPI :: forall srv name
. HasAPI srv name
=> APICall srv name
= unsafeCallAPI callAPI
```

Any code written against the old `HasAPI`

constraint will continue to work (modulo the instance definitions,) but concrete calls to `callAPI`

now result in a dangling, unsatisfiable `CallsAPI`

constraint. Youâll need to go through the codebase now, and document every transitive call to the API with matching `CallsAPI`

constraints. Thankfully, HLS can help with this task: it will underline the missing cases, and suggest a code action that will automatically add these constraints to the type. Rinse and repeat, until every code path is documented.

Great success! We have automatically found every codepath that makes an API call, and forced them to document that fact. Better yet, we have solved the problem once and for all; our coworkers also must document any new API calls they make, lest their code not compile. It seems like weâre done!

Except for one fact: GHC will rudely refuse to compile our project, even if we correctly track all of our API calls. The problem of course, is that all we have managed to do is force `main`

to collect every `CallsAPI`

constraint. But GHC will still complain `No instance for (CallsAPI srv name)`

. Of course, you could just give an orphan instance in the same module that defines `main`

, which would work, but this doesnât give you any sort of *external documentation.* Itâs nice when you read the code, but it doesnât help the business people.

A better approach here is to selectively solve the `CallsAPI`

constraints, which we can do with some Haskell dark magic. The `Dict`

type captures a constraint, giving us a convenient way to manipulate constraints:

```
type Dict :: Constraint -> Type
data Dict c where
Dict :: c => Dict c
```

We can write an eliminator to bring the `c`

from a `Dict c`

into scope, which, importantly, allows us to solve otherwise-unsolved constraints:

```
:: (c => r) -> Dict c -> r
(\\)Dict = f f \\
```

If we can get our hands on a `Dict (CallsAPI Srv Name)`

, we can use `(\\)`

to convince GHC to compile our program.

GHC is happy to give us dictionaries for constraints it knows about:

```
showIntDict :: Dict (Show Int)
= Dict showIntDict
```

but unfortunately, refuses to give us dictionaries for unsolved constraints:

```
callsAPIDict :: forall srv name. Dict (CallsAPI srv name)
= Dict
callsAPIDict
-- Error: No instance for (CallsAPI srv name)
```

It seems like weâre just as stuck, but we have a trick up our sleeve. The first step is to define another class with an instance in scope. GHC will happily give us a dictionary for such a thing:

```
class Trivial
instance Trivial
trivialDict :: Dict Trivial
= Dict trivialDict
```

and now for something naughty:

```
callsAPIDict :: forall srv name. Dict (CallsAPI srv name)
= unsafeCoerce trivialDict callsAPIDict
```

Behind the scenes, GHC compiles classes into records, instances into values of these records, and replaces wanted constraints with function arguments taking those records. By ensuring that `Trivial`

and `CallsAPI`

are both empty classes, with no methods or super-classes, we can be certain the generated records for these classes will be identical, and thus that it is OK to coerce one into the other.

Armed with `withDict`

and `callsAPIDict`

, we can play the part of the constraint solver and satisfy constraints ourself. GHC will happily compile the following example:

```
ex :: HasAPI ShoutService "shout" => IO String
= callAPI @ShoutService @"shout" "hello world"
ex
-- Look ma, no HasAPI constraint!
test :: IO String
= ex \\ callsAPIDict @ShoutService @"shout" test
```

So thatâs the rough technique. But how do we actually use it in anger?

Our actual use case at work is to add these API calls to our swagger documentation. Swagger is this automatically generated manifest of an API surface; we want to document the fact that some API calls might call other ones. Our server is one big servant application, and servant is extensible. So the real technique is to build a servant combinator that eliminates `HasAPI`

constraints when you document them in the API definition.

Getting into the nitty gritty bits of servant is beyond the scope of this post, but we can sketch the idea. Servant APIs use the type-level `(:>)`

operator to combine information about an endpoint. For example, we might expose another service:

```
type ServantAPI = "api" :>
"echo"
:> ReqBody '[JSON] String
:> Get '[JSON] String
```

This definition states that we have a REST server with a single route, `api/echo`

which responds to `POST`

requests, returning a JSON-encoded string, which takes a JSON-encoded string as the request body.

A servant server for `ServantAPI`

would have type `Server ServantAPI`

, where `Server`

is a type family given by `HasServer`

. Evaluating the type family results in `String -> Handler String`

, so in order to implement this server, we would need to provide a function of that type.

Letâs implement our server endpoint:

```
echo :: CallsAPI ShoutService "shout"
=> String
-> Handler String
= liftIO $ callAPI @ShoutService @"shout" str echo str
```

Unfortunately, due to our earlier work, we canât eliminate the `CallsAPI`

constraint, and thus we canât actually use `echo`

as the handler for our endpoint.

Itâs important to note that servantâs DSL is extensible, and we can add our own machinery here. The first step is to build a type that we can use in servant:

```
type MakesAPICall :: Service -> Symbol -> Type
data MakesAPICall srv name
```

We can now build a second version of `ServantAPI`

:

```
type ServantAPI = "api" :>
"echo"
:> MakesAPICall ShoutService "shout"
:> ReqBody '[JSON] String
:> Get '[JSON] String
```

In order to actually run our endpoint, we need to give an instance of `HasServer`

for our new `MakesAPICall`

combinator:

```
instance HasServer api ctx
=> HasServer (MakesAPICall srv name :> api) ctx
where
type ServerT (MakesAPICall srv name :> api) m =
Dict (CallsFed srv name) -> ServerT api m
=
route _ ctx f Proxy @api) ctx $ fmap ($ callsAPIDict @srv @name) f route (
```

The `ServerT`

instance here adds a `Dict (CallsFed srv name)`

to the type of the handler required to satisfy this endpoint, while `route`

automatically fills in the dictionary whenever the handler needs to be run. In an ideal world, we could give our `ServerT`

instance as:

```
type ServerT (MakesAPICall srv name :> api) m =
CallsFed srv name => ServerT api m
```

but GHC doesnât let us use quantified types on the right-hand sides of type families, so this is unfortunately a no-go. Playing games with `Dict`

instead is the best approach Iâve found here, but Iâd love to hear if anyone has a better idea.

We still canât use `echo`

as a handler, but we can use `makesCall echo`

as one, where `makesCall`

is given as:

```
makesCall :: (c => r) -> Dict c -> r
= (\\) makesCall
```

Servers that document their API calls via `MakesAPICall`

and which wrap their handlers with `makesCall`

can now eliminate `CallsFed`

constraints. Since this is the only way of eliminating `CallsFed`

constraints, we can be sure that every API call is correctly documented in the servant DSL!

The final step here is to add an instance of `HasSwagger (MakesAPICall srv name :> api)`

, but the details are gory and devoid of educational value. Suffice it to say that this instance was written, and now we have automatically generated JSON documentation describing which server endpoints make which other API calls. This documentation is guaranteed to be correct, because updating it is the only way to convince GHC to compile your code.

Contrast this to the internet of yore. By virtue of being hard to access, the internet filtered away the mass appeal it has today. It was hard and expensive to get on, and in the absence of authoring tools, you were only creating internet content if you *had something to say.* Which meant that, as a consumer, if you found something, you had good reason to believe it was well-informed. Why would someone go through the hassle of making a website about something they werenât interested in?

In 2022, we have a resoundingly sad answer to that question: advertising. The primary purpose of the web today is âengagement,â which is Silicon Valley jargon for âhow many ads can we push through someoneâs optical nerve?â Under the purview of engagement, it makes sense to publish webpages on every topic imaginable, regardless of whether or not you know what youâre talking about. In fact, engagement goes up if you *donât* know what youâre talking about; your poor reader might mistakenly believe that theyâll find the answer theyâre looking for elsewhere on your site. Thatâs twice the advertising revenue, baby!

But the spirit of the early web isnât gone: the bookmarks Iâve kept these long decades mostly still work, and many of them still receive new content. Thereâs still weird, amateur, passion-project stuff out there. Itâs just hard to find. Which brings us to our main topic: search.

Google is inarguably the front page of the internet. Maybe you already know where your next destination is, in which case you probably search for the website on Google and click on the first link, rather than typing in the address yourself. Or maybe you donât already know your destination, and you search for it. Either way, you hit Google first.

When I say the internet is getting worse, what I really mean is that the Google search results are significantly less helpful than they used to be. This requires some qualification. Google has gotten exceedingly good at organizing everyday life. It reliably gets me news, recipes, bus schedules, tickets for local events, sports scores, simple facts, popular culture, official regulations, and access to businesses. Itâs essentially the yellow pages and the newspaper put together. For queries like this, which are probably 95% of Googles traffic, Google does an excellent job.

The difficulties come in for that other 5%, the so-called âlong tail.â The long tail is all those other things we want to know about. Things without well-established, factual answers. Opinions. Abstract ideas. Technical information. If youâre cynical, perhaps itâs all the stuff that doesnât have wide-enough appeal to drive engagement. Whatever the reason, the long tail is the stuff thatâs hard to find on the modern internet.

Notice that the long-tail is exactly the stuff we need search for. Mass-appeal queries are, almost by definition, not particularly hard to find. If I need a bus schedule, I know to talk to my local transit authority. If Iâm looking to keep up with the Kardashians, Iâm not going to have any problems (at least, no *search* problems.) On the other hand, itâs much less clear where to get information on why my phone starts overheating when I open the chess app.

So what happens if you search for the long tail on Google? If youâre like me, you flail around for ten minutes wasting your time reading crap articles before you remember that Google is awful for the long tail, and you come away significantly more frustrated, not having found what you were looking for in the first place.

Lets look at some examples. One of my favorite places in the world is Koh Lanta, Thailand. When traveling, Iâm always on the lookout for places that give off the Koh Lanta vibe. What does that mean? Hard to say, exactly, but having tourist amenities without being touristy. Charming, slow, cheap. I donât know exactly; if I did, itâd be easier to find. Anyway, forgetting that Google is bad at long tails, I search for `what is the koh lanta of croatia?`

and get:

- Koh-Lanta - Wikipedia [note: not the island, the game show]
- Top 15 Unique Things to Do in Koh Lanta
- Visit Koh Lanta on a trip to Thailand
- Beautiful places to travel, Koh lanta, Sunset
- Holiday Vacation to Koh Lanta: Our favourite beaches and âŚ
- Koh Lanta Activities: 20 Best Things to Do
- etc

With the exception of âfind a flight from Dubrovnik to Koh Lantaâ on page two, you need to get to page five before you see any results that even acknowledge I *also* searched for `croatia`

. Not very impressive.

When you start paying attention, youâll notice it on almost every search â Google isnât actually giving you answers to things you searched for. Now, maybe the reason here is that there *arenât* any good results for the query, but thatâs a valuable thing to know as well. Donât just hit me with garbage, itâs an insult to my intelligence and time.

I wanted to figure out why exactly the internet is getting worse. Whatâs going on with Googleâs algorithm that leads to such a monotonous, boring, corporate internet landscape? I thought Iâd dig into search engine optimization (SEO) â essentially, techniques that improve a websiteâs ranking in Google searches. Iâd always thought SEO was better at selling itself than it was at improving search results, but my god was I wrong.

SEO techniques are extremely potent, and their widespread adoption is whatâs wrong with the modern web.

For example, have you ever noticed that the main content of most websites is something like 70% down the page? Every recipe site Iâve ever seen is like this â nobody cares about how this recipe was originally your great-grandmotherâs. Just tell us whatâs in it. Why is this so prevalent on the web?

Google rewards a website for how long a user stays on it, with the reasoning being that a bad website has the user immediately hit the back button. Seems reasonable, until you notice the problem of incentives here. Websites arenât being rewarded for having good content under this scheme, theyâre rewarded for wasting your time and making information hard to find. Outcome: websites that answer questions, but hide the information somewhere on a giant (ad-filled) page.

Relatedly, have you noticed how every website begins with a stupid paragraph overviewing the thing youâre searching for? Itâs always followed by a stupid paragraph describing why you should care about the thing. For example, I just searched for `garden irrigation`

, and the first result is:

Water is vital to plant health, but watering by hand can be a hassle. You have to drag hoses between gardens, move sprinklers around, or take the time to water each plant. Our innovative watering systems take the hassle out of watering. Theyâre the easiest way to give plants the consistent moisture they need for your biggest harvest and most beautiful blooms.

*Water is vital to plant health.* Wow, who knew! Why in godâs name would I be searching for garden irrigation if I didnât know that water was vital to plant health. Why is copy like this so prevalent on the web?

Things become clearer when you look at some of the context of this page:

Url: https://[redacted]/how-to/how-to-choose-a-watering-system/8747.html

Title: How to Choose a Garden Irrigation System

Heading: Soak, Drip or Spray: Which is right for you?

Subheading: Choose the best of our easy, customizable, irrigation systems to help your plants thrive and save water

As it happens, Google rewards websites which use keywords in their url, title, headings, and first 100 words. Just by eyeballing, we can see that this particular website is targeting the keywords âwaterâ, âsystemâ, âirrigationâ, and âgardenâ. Pages like these hyper-optimized to come up for particular searches. The stupid expository stuff exists only to pack âimportant keywordsâ into the first 100 words.

But keyword targeting doesnât stop there. As I was reading through this SEO stuff (that is, the first page of a Google search for `seo tricks`

,) every single page offered 15-25 great, technical SEO tricks. And then, without fail, the final point on each page was âbut really, the best SEO strategy is having great content!â Thatâs weird. âGreat contentâ isnât something an algorithm can identify; if it were, you wouldnât be currently reading the ravings of a madman, angry about the state of the internet.

So, why do all of these highly-optimized SEO pages ubiquitously break form, switching from concrete techniques to platitudes? You guessed it, itâs a SEO technique! Google offers a keyword dashboard, where you can see which keywords group together, and (shudder) which keywords are *trending.* Google rewards you for having other keywords in the group on your page. And it extra rewards you for having trending keywords. You will not be surprised to learn that âquality contentâ is a keyword that clusters with âseo,â nor that it is currently a trending keyword.

Think about that for a moment. Under this policy, Google is incentivizing pages to become *less focused,* by adding text that is only tangentially related. But, how do related keywords come about? The only possible answer here is to find keywords that often cluster on other pages. But this is a classic death spiral, pulling every page in a topic to have the same content.

Another way of looking at it is that if you are being incentivized, you are being *disincentivized.* Webpages are being penalized for including original information, because original information canât possibly be in the keyword cluster.

There are a multitude of perverse incentives from Google, but Iâll mention only two more. The first is that websites are penalized for having low-ranking pages. The conventional advice here is to delete âunderperformingâ pages, which only makes the search problem worse â sites are being rewarded for deleting pages that donât align with the current search algorithm.

My last point: websites are penalized for even *linking* to low-ranking pages!

Itâs not hard to put all of the pieces together and see why the modern web is so bland and monotonous. Not only is the front-page of the internet aggressively penalizing websites which *arenât* bland and monotonous, itâs also punishing any site which has the audacity to link to more interesting parts of the web.

So the discoverable part of web sucks. But is that really Googleâs fault? Iâd argue no. By virtue of being the front-page, Googleâs search results are under extreme scrutiny. In the eyes of the non-technical population, especially the older generations, the internet and Google are synonymous. The fact is that Google gets unfairly targeted by legislation because itâs a big, powerful tech company, and we as a society are uncomfortable with that.

Worse, the guys doing the regulation donât exactly have a grasp on how internet things work.

Society at large has been getting very worried about disinformation. Whoâs problem is that? Googleâs â duh. Google is how we get information on the internet, so itâs up to them to defend us from disinformation.

Unfortunately itâs really hard to spot disinformation. Sometimes even the *government* lies to us (gasp!). I can think of two ways of avoiding getting in trouble with respect to disinformation. One: link only to *official sites,* thus changing the problem of trustworthiness to one of authority. If there is no authority, just give back the consensus. Two: donât return any information whatsoever.

Googleâs current strategy seems to be somewhere between one and two. For example, we can try a controversialish search like `long covid doesn't exist`

. The top results at time of writing are:

- The search for Long Covid (science.org)
- Small Study Finds No Obvious Physical Causes for Long COVID (medscape.com)
- Fact Check-âLong COVIDâ is not fake, quoted French study did âŚ (reuters.com)
- Harvard Medical School expert explains âlong COVIDâ (harvard.edu)
- Claim that French study showed long COVID doesnât exist âŚ (healthfeedback.org)
- What doctors wish patients knew about long COVID (ama-assn.org)

Iâm not particularly in the know, but I recognize most of these organizations. Science.org sounds official. Not only is one of the pages from Harvard, but also itâs from a Harvard Medical School *expert.* I especially like the fifth one, the metadata says:

Claim: Long COVID is âmostly a mental diseaseâ; the condition long COVID is solely due to a personâs belief, not actual disease; long COVID doesnât exist

Fact check by Health Feedback: Inaccurate

Every one of these websites comes off as *authoritative* â not in sense of âknowing what theyâre talking aboutâ because thatâs hard to verify â but in the sense of being the sort of organization weâd trust to answer this question for us. Or, in the case of number five, at least telling us that they fact checked it.

Letâs try a search for something requiring less authority, like âbest books.â In the past I would get a list of books considered the best. But now I get:

- The Greatest Books: The Best Books of All Time - 1 to 50
- The Best Books of All Time | chapters.indigo.ca
- 100 Best Books of All Time - Readerâs Digest
- Best Book Lists - Goodreads
- Best Books 2022: Books We Love : NPR

Youâll notice there are no actual books here. There are only *lists* of best books. Cynical me notes that if you were to actually list a book, someone could find it controversial. Instead, you can link to institutional websites, and let them take the controversy for their picks.

This isnât the way the web needs to be. Google could just as well given me personal blogs of people talking about long covid and their favorite books, except (says cynical me) that these arenât authoritative sources, and thus, linking to them could be considered endorsement. And the web is too big and too fast moving to risk linking to anything that hasnât been vetted in advance. Itâs just too easy to accidentally give a *good* result to a controversial topic, and have the law makers pounce on you. Instead, punt the problem back to authorities.

The web promised us a democratic, decentralized public forum, and all we got was the stinking yellow pages in digital format. I hope the crypto people can learn a lesson here.

Anyway, all of this is to say that I think lawmakers and liability concerns are the real reason the web sucks. All things being equal, Google would like to give us good results, but it prefers making boatloads of money, and that would be hard to do if it got regulated into nothingness.

Google isnât the only search engine around. There are others, but itâs fascinating that none of them compete on the basis of providing better results. DDG claims to have better privacy. Ecosia claims to plant trees. Bing exists to keep Microsoft relevant post-2010, and for some reason, ranks websites for being highly-shared on social media (again, things that are, by definition, not hard to find.)

Why donât other search engines compete on search results? It canât be hard to do better than Google for the long tail.

Itâs interesting to note that the problems of regulatory-fear and SEO-capture are functions of Googleâs cultural significance. If Google were smaller or less important, thereâd be significantly less negative-optimization pressure on it. Google is a victim of its own success.

That is to say, I donât think all search engines are doomed to fail in the same way that Google has. A small search engine doesnât need to be authoritative, because nobody is paying attention to it. And it doesnât have to worry about SEO for the same reason â thereâs no money to be made in manipulating its results.

What I dream of is Google circa 2006. A time where a search engine searched what you asked for. A time before aggressive SEO. A time before social media, when the only people on the internet had a reason to be there. A time before sticky headers and full-screen modal pop-ups asking you to subscribe to a newsletter before reading the article. A time before click-bait and subscription-only websites which tease you with a paragraph before blurring out the rest of the content.

These problems are all solvable with by a search engine. But that search engine isnât going to be Google. Letâs de-rank awful sites, and boost personal blogs of people with interesting things to say. Letâs de-rank any website that contains ads. Letâs not index any click-bait websites, which unfortunately in 2022 includes most of the news.

What we need is a search engine, by the people, and for the people. Fuck the corporate interests and the regulatory bullshit. None of this is hard to do. It just requires someone to get started.

]]>A few months ago, the excellent David Rusu gave me an impromptu lecture on ring signatures, which are a way of signing something as an anonymous member of a group. That is, you can show someone in the signing pool was actually responsible for signing the thing, but canât determine *which member of the pool actually signed it.* David walked me through all the math as to how that actually happens, but I was unable to follow it, because the math was hard and, perhaps more importantly, it felt like hand-compiling a proof.

What do I mean by âhand-compilingâ a proof? Well, we have some mathematical object, something like

postulate Identity : Set Message : Set SignedBy : Message â Identity â Set use-your-imagination : {A : Set} â A record SignedMessage {n : â} (pool : Vec Identity n) : Set where field message : Message @erased signer : Fin n signature : SignedBy message (lookup pool signer)

where `@erased`

is Agdaâs runtime irrelevance annotation, meaning the signer field wonât exist at runtime. In fact, attempting to write a function that would extract it results in the following error:

Identifier

`signer`

is declared erased, so it cannot be used here

when checking that the expression`signer x`

has type`Fin n`

Nice one Agda!

Hand-compiling this thing is thus constructing some object that has the desired properties, but doing it in a way that requires BEING VERY SMART, and throwing away any chance at composability in the process. For example, itâd be nice to have the following:

open SignedMessage weakenL : â {n pool new-id} â SignedMessage {n} pool â SignedMessage (new-id âˇ pool) weakenL x = use-your-imagination weakenR : â {n pool new-id} â SignedMessage {n} pool â SignedMessage (pool ++ [ new-id ]) weakenR x = use-your-imagination

which would allow us to arbitrarily extend the pool of a signed message. Then, we could trivially construct one:

sign : Message â (who : Identity) â SignedMessage [ who ] message (sign msg who) = msg signer (sign msg who) = zero signature (sign msg who) = use-your-imagination

and then obfuscate who signed by some random choice of subsequent weakenLs and weakenRs.

Unfortunately, this is not the case with ring signatures. Ring signatures require you to âbake inâ the signing pool when you construct your signature, and you can never again change that pool, short of doing all the work again. This behavior is non-composable, and thus, in my reckoning, unlikely to be a true solution to the problem.

The paper I chose to review this week is Proof-Carrying Code by George Necula, in an attempt to understand if the PL literature has anything to say about this problem.

PCC is an old paper (from 1997, egads!) but it was the first thing I found on the subject. I should really get better at vetting my literature before I go through the effort of going through it, but hey, what are you going to do?

The idea behind PCC is that we want to execute some untrusted machine code. But we donât want to sacrifice our system security to do it. And we donât want to evaluate some safe language into machine code, because that would be too slow. Instead, weâll send the machine code, as well as a safety proof that verifies itâs safe to execute this code. The safety proof is tied to the machine code, such that you canât just generate a safety proof for an unrelated problem, and then attach it to some malicious code. But the safety proof isnât obfuscated or anything; the claim is that if you can construct a safety proof for a given program, that program is necessarily safe to run.

On the runtime side, there is a simple algorithm for checking the safety proof, and it is independent of the arguments that the program is run with; therefore, we can get away with checking code once and evaluating it many times. Itâs important that the algorithm be simple, because itâs a necessarily trusted piece of code, and it would be bad news if it were to have bugs.

PCCâs approach is a bitâŚ unimaginative. For every opcode weâd like to allow in the programs, we attach a safety precondition, and a postcondition. Then, we map the vector of opcodes weâd like to run into its pre/post conditions, and make sure they are confluent. If they are, weâre good to go. This vector of conditions is called the vector VC in the paper.

So, the compiler computes the VC and attaches it to the code. Think of the VC as a proposition of safety (that is, a type), and a proof of that proposition (the VC itself.) In order to validate this, the runtime does a safety typecheck, figuring out what the proposition of safety would have to be. It compares this against the attached proof, and if they match, it typechecks the VC to ensure it has the type it says. If it does, our code is safe.

The PCC paper is a bit light on details here, so itâs worth thinking about exactly whatâs going on here. Presumably determining the safety preconditions is an easy problem if we can do it at runtime, but proving some code satisfies it is hard, *or else we could just do that at runtime too.*

Iâm a bit hesitant to dive into the details here, because I donât really care about determining whether some blob of machine code is safe to run. Itâs a big ball of poorly typed typing judgments about memory usage. Why do I say poorly typed? Well consider one of the rules from the paper:

$\frac{m \vdash e : \tau \text{list} \quad \quad e \neq 0} {m \vdash e : \text{addr} \wedge \ldots}$

Here we have that from `e : List Ď`

(and that `e`

isnât 0) we can derive `e : addr`

. At best, if we are charitable in assuming $e \neq 0$ means that `e`

isnât `nil`

, there is a type preservation error here. If we are less charitable, there is also some awful type error here involving 0, which might be a null check or something? This seems sufficiently messy that I donât care enough to decipher it.

How applicable is any of this to our original question around ring signatures? Not very, I think, unfortunately. We already have the ring signature math if weâd like to encode a proof, and the verification of it is easy enough. But itâs still not very composable, and I doubt this paper will add much there. Some more promising approaches would be to draw the mystery commutative diagrams ala Adders and Arrows, starting from a specification and deriving a chain of proofs that the eventual implementation satisfies the specification. The value there is in all the intermediary nodes of the commutative diagram, and whether we can prove weakening lemmas there.

But PCC isnât entirely a loss; I learned about `@erased`

in Agda.

I was describing my idea from last week to automatically optimize programs to Colin, who pointed me towards Syntax-Guided Synthesis by Alur et al.

Syntax-Guided Synthesis is the idea that free-range program synthesis is really hard, so instead, letâs constrain the search space with a grammar of allowable programs. We can then enumerate those possible programs, attempting to find one that satisfies some constraints. The idea is quite straightforward when you see it, but thatâs not to say itâs unimpressive; the paper has lots of quantitative results about exactly how well this approach does.

The idea is we want to find programs with type I `â`

O, that satisfy some specification. Weâll do that by picking some Language of syntax, and trying to build our programs there.

All of this is sorta moot, because we assume we have some oracle which can tell us if our program satisfies the spec. But the oracle is probably some SMT solver, and is thus expensive to call, so weâd like to try hard not to call it if possible.

Letâs take an example, and say that weâd like to synthesize the `max`

of two `Nat`

s. There are lots of ways of doing that! But weâd like to find a function that satisfies the following:

data MaxSpec (f : â Ă â â â) : â Ă â â Set where is-max : {x y : â} â x â¤ f (x , y) â y â¤ f (x , y) â ((f (x , y) âĄ x) â (f (x , y) âĄ y)) â MaxSpec f (x , y)

If we can successfully produce an element of MaxSpec `f`

, we have a proof that `f`

is an implementation of `max`

. Of course, actually producing such a thing is rather tricky; itâs equivalent to determining if MaxSpec `f`

is Decidable for the given input.

In the first three cases, we have some conflicting piece of information, so we are unable to produce a MaxSpec:

decideMax : (f : â Ă â â â) â (i : â Ă â) â Dec (MaxSpec f i) decideMax f i@(x , y) with f i | inspect f i ... | o | [ fiâĄo ] with x â¤? o | y â¤? o ... | no ÂŹxâ¤o | _ = no Îť { (is-max xâ¤o _ _) â contradiction (â¤-trans xâ¤o (â¤-reflexive fiâĄo)) ÂŹxâ¤o } ... | yes _ | no ÂŹyâ¤o = no Îť { (is-max x yâ¤o xâ) â contradiction (â¤-trans yâ¤o (â¤-reflexive fiâĄo)) ÂŹyâ¤o } ... | yes xâ¤o | yes yâ¤o with o â x | o â y ... | no xâ o | no yâ o = no Îť { (is-max x xâ (injâ xâ)) â contradiction (trans (sym fiâĄo) xâ) xâ o ; (is-max x xâ (injâ y)) â contradiction (trans (sym fiâĄo) y) yâ o }

Otherwise, we have a proof that `o`

is equal to either `y`

or `x`

:

... | no proof | yes oâĄy = yes (is-max (â¤-trans xâ¤o (â¤-reflexive (sym fiâĄo))) (â¤-trans yâ¤o (â¤-reflexive (sym fiâĄo))) (injâ (trans fiâĄo oâĄy))) ... | yes oâĄx | _ = yes (is-max (â¤-trans xâ¤o (â¤-reflexive (sym fiâĄo))) (â¤-trans yâ¤o (â¤-reflexive (sym fiâĄo))) (injâ (trans fiâĄo oâĄx)))

MaxSpec is a proof that our function is an implementation of `max`

, and decideMax is a proof that âweâd know one if we saw one.â So thatâs the specification taken care of. The next step is to define the syntax weâd like to guard our search.

The paper presents this syntax as a BNF grammar, but my thought is why use a grammar when we could instead use a type system? Our syntax is a tiny little branching calculus, capable of representing Terms and branching Conditionals:

mutual data Term : Set where var-x : Term var-y : Term const : â â Term if-then-else : Cond â Term â Term â Term data Cond : Set where leq : Term â Term â Cond and : Cond â Cond â Cond invert : Cond â Cond

All thatâs left for our example is the ability to âcompileâ a Term down to a candidate function. Just pattern match on the constructors and push the inputs around until weâre done:

mutual eval : Term â â Ă â â â eval var-x (x , y) = x eval var-y (x , y) = y eval (const c) (x , y) = c eval (if-then-else c t f) i = if evalCond c i then eval t i else eval f i evalCond : Cond â â Ă â â Bool evalCond (leq m n) i = Dec.does (eval m i â¤? eval n i) evalCond (and c1 c2) i = evalCond c1 i â§ evalCond c2 i evalCond (invert c) i = not (evalCond c i)

So thatâs most of the idea; weâve specified what weâre looking for, via MaxSpec, what our syntax is, via Term, and a way of compiling our syntax into functions, via eval. This is the gist of the technique; the rest is just algorithms.

The paper presents several algorithms and evaluates their performances. But one is clearly better than the others in the included benchmarks, so weâll just go through that one.

Our algorithm to synthesize code corresponding to the specification takes a few parameters. Weâve seen the first few:

module Solver {Lang I O : Set} (spec : (I â O) â I â Set) (decide : (f : I â O) â (i : I) â Dec (spec f i)) (compile : Lang â I â O)

However, we also need a way of synthesizing terms in our Language. For that, weâll use enumerate, which maps a natural number to a term:

(enumerate : â â Lang)

Although itâs not necessary for the algorithm, we should be able to implement exhaustive over enumerate, which states every Lang is eventually produced by enumerate:

(exhaustive : (x : Lang) â ÎŁ[ n â â ] (enumerate n âĄ x))

Finally, we need an oracle capable of telling us if our solution is correct. This might sound a bit like cheating, but behind the scenes itâs just a magic SMT solver. The idea is that SMT can either confirm that our program is correct, or produce a counterexample that violates the spec. The type here is a bit crazy, so weâll take it one step at a time.

An oracle is a function that takes a LangâŚ

(oracle : (exp : Lang)

and either gives back a function that can produce a `spec (compile exp)`

for every input:

â ((i : I) â spec (compile exp) i)

or gives back some input which is not a `spec (compile exp)`

:

â ÎŁ[ i â I ] ÂŹ spec (compile exp) i) where

The algorithm here is actually quite clever. The idea is that to try each enumerated value in order, attempting to minimize the number of calls we make to the oracle, because theyâre expensive. So instead, well keep a list of every counterexample weâve seen so far, and ensure that our synthesized function passes all of them before sending it off to the oracle. First, weâll need a data structure to store our search progress:

record SearchState : Set where field iteration : â cases : List I open SearchState

The initial search state is one in which we start at the beginning, and have no counterexamples:

start : SearchState iteration start = 0 cases start = []

We can try a function by testing every counterexample:

try : (I â O) â List I â Bool try f = all (Dec.does â decide f)

and finally, can now attempt to synthesize some code. Our function check takes a SearchState, and either gives back the next step of the search, or some program, and a proof that itâs what weâre looking for.

check : SearchState â SearchState â (ÎŁ[ exp â Lang ] ((i : I) â spec (compile exp) i)) check ss

We begin by getting and compiling the next enumerated term:

with enumerate (iteration ss) ... | exp with compile exp

check if it passes all the previous counterexamples:

... | f with try f (cases ss)

if it doesnât, just fail with the next iteration:

... | false = injâ (record { iteration = suc (iteration ss) ; cases = cases ss })

Otherwise, our proposed function might just be the thing weâre looking for, so itâs time to consult the oracle:

... | true with oracle exp

which either gives a counterexample that we need to record:

... | injâ (y , _) = injâ (record { iteration = suc (iteration ss) ; cases = y âˇ cases ss })

or it confirms that our function satisfies the specification, and thus that were done:

... | injâ x = injâ (exp , x)

Pretty cool! The paper gives an optimization that caches the result of every counterexample on every synthesized program, and reuses these whenever that program appears as a subprogram of a larger one. The idea is that we can trade storage so we only ever need to evaluate each subprogram once â important for expensive computations.

Of course, pumping check by hand is annoying, so we can instead package it up as solve which takes a search depth, and iterates check until it runs out of gas or gets the right answer:

solve : â â Maybe (ÎŁ[ exp â Lang ] ((i : I) â spec (compile exp) i)) solve = go start where go : SearchState â â â Maybe (ÎŁ Lang (Îť exp â (i : I) â spec (compile exp) i)) go ss zero = nothing go ss (suc n) with check ss ... | injâ x = go ss n ... | injâ y = just y]]>

Today weâre heading back into the Elliottverse â a beautiful world where programming is principled and makes sense. The paper of the week is Conal Elliottâs Generic Parallel Functional Programming, which productively addresses the duality between âeasy to reason aboutâ and âfast to run.â

Consider the case of a right-associated list, we can give a scan of it in linear time and constant space:

module ExR where data RList (A : Set) : Set where RNil : RList A _â_ : A â RList A â RList A infixr 5 _â_ scanR : âŚ Monoid A âŚ â RList A â RList A scanR = go mempty where go : âŚ Monoid A âŚ â A â RList A â RList A go acc RNil = RNil go acc (x â xs) = acc â go (acc <> x) xs

This is a nice functional algorithm that runs in $O(n)$ time, and requires $O(1)$ space. However, consider the equivalent algorithm over left-associative lists:

module ExL where data LList (A : Set) : Set where LNil : LList A _âˇ_ : LList A â A â LList A infixl 5 _âˇ_ scanL : âŚ Monoid A âŚ â LList A â LList A scanL = projâ â go where go : âŚ Monoid A âŚ â LList A â LList A Ă A go LNil = LNil , mempty go (xs âˇ x) = let xs' , acc = go xs in xs' âˇ acc , x <> acc

While scanL is also $O(n)$ in its runtime, it is not amenable to tail call optimization, and thus also requires $O(n)$ *space.* Egads!

You are probably not amazed to learn that different ways of structuring data lead to different runtime and space complexities. But itâs a more interesting puzzle than it sounds; because RList and LList are isomorphic! So what gives?

Reedâs pithy description here is

Computation time doesnât respect isos

Exploring that question with him has been very illuminating. Math is deeply about extentionality; two mathematical objects are equivalent if their abstract interfaces are indistinguishable. ComputationâŚ doesnât have this property. When computing, we care a great deal about runtime performance, which depends on fiddly implementation details, even if those arenât externally observable.

In fact, as he goes on to state, this is the whole idea of denotational design. Figure out the extensional behavior first, and then figure out how to implement it.

This all harkens back to my review of another of Elliottâs papers, Adders and Arrows, which starts from the extensional behavior of natural addition (encoded as the Peano naturals), and then derives a chain of proofs showing that our everyday binary adders preserve this behavior.

Anyway, letâs switch topics and consider a weird fact of the world. Why do so many parallel algorithms require gnarly array indexing? Hereâs an example I found by googling for âparallel c algorithms cudaâ:

```
void stencil_1d(int *in, int *out) {
__global__ int temp[BLOCK_SIZE + 2 * RADIUS];
__shared__ int gindex = threadIdx.x + blockIdx.x * blockDim.x;
int lindex = threadIdx.x + RADIUS;
[lindex] = in[gindex];
tempif (threadIdx.x < RADIUS) {
[lindex - RADIUS] = in[gindex - RADIUS];
temp[lindex + BLOCK_SIZE] =
temp[gindex + BLOCK_SIZE];
in}
();
__syncthreadsint result = 0;
for (int offset = -RADIUS ; offset <= RADIUS ; offset++)
+= temp[lindex + offset];
result [gindex] = result;
out}
```

and hereâs another, expressed as an âeasy inductionâ recurrence relation, from Richard E Ladner and Michael J Fischer. Parallel prefix computation:

Sweet lord. No wonder weâre all stuck pretending our computer machines are single threaded behemoths from the 1960s. Taking full advantage of parallelism on modern CPUs must require a research team and five years!

But itâs worth taking a moment and thinking about what all of this janky indexing must be doing. Whatever algorithm is telling the programmer which indices to write where necessarily must be providing a view on the data. That is, the programmer has some sort of âshapeâ in mind for how the problem should be subdivided, and the indexing is an implementation of accessing the raw array elements in the desired shape.

At risk of beating you on the head with it, this array indexing is *a bad implementation of a type system.* Bad because itâs something the implementer needed to invent by hand, and is not in any form that the compiler can help ensure the correctness of.

That returns us to the big contribution of *Generic Function Parallel Algorithms,* which is a technique for decoupling the main thrust of an algorithm from extentionally-inconsequential encodings of things. The idea is to implement the algorithm on lots of trivial data structures, and then compose those small pieces together to get a *class* of algorithms.

The first step is to determine which trivial data structures we need to support. Following the steps of Haskellâs `GHC.Generics`

module, we can decompose any Haskell98 data type as compositions of the following pieces:

data Rep : Setâ where V : Rep U : Rep K : Set â Rep Par : Rep Rec : (Set â Set) â Rep _:+:_ : Rep â Rep â Rep _:*:_ : Rep â Rep â Rep _:â:_ : Rep â Rep â Rep

which we can embed in Set via Represent:

open import Data.Empty open import Data.Sum open import Data.Unit hiding (_â¤_) record Compose (F G : Set â Set) (A : Set) : Set where constructor compose field composed : F (G A) open Compose Represent : Rep â Set â Set Represent V a = âĽ Represent U a = â¤ Represent (K x) a = x Represent Par a = a Represent (Rec f) a = f a Represent (x :+: y) a = Represent x a â Represent y a Represent (x :*: y) a = Represent x a Ă Represent y a Represent (x :â: y) a = Compose (Represent x) (Represent y) a

If youâve ever worked with `GHC.Generics`

, none of this should be very exciting. We can bundle everything together, plus an iso to transform to and from the Represented type:

record Generic (F : Set â Set) : Setâ where field RepOf : Rep from : F A â Represent RepOf A to : Represent RepOf A â F A open Generic âŚ ... âŚ GenericRep : (F : Set â Set) â âŚ Generic F âŚ â Set â Set GenericRep _ = Represent RepOf

Agda doesnât have any out-of-the-box notion of `-XDeriveGeneric`

, which seems like a headache at first blush. It means we need to explicitly write out a RepOf and from/to pairs by hand, *like peasants.* Surprisingly however, needing to implement by hand is beneficial, as it reminds us that RepOf *is not uniquely determined!*

A good metaphor here is the number 16, which stands for some type weâd like to generify. A RepOf for 16 is an equivalent representation for 16. Here are a few:

- $2+(2+(2+(2+(2+(2+(2+2))))))$
- $((2+2)*2)+(((2+2)+2)+2)$
- $2 \times 8$
- $8 \times 2$
- $(4 \times 2) \times 2$
- $(2 \times 4) \times 2$
- $4 \times 4$
- $2^4$
- $2^{2^2}$

And there are lots more! Each of $+$, $\times$ and exponentiation corresponds to a different way of building a type, so every one of these expressions is a distinct (if isomorphic) type with 16 values. Every single possible factoring of 16 corresponds to a different way of dividing-and-conquering, which is to say, a different (but related) algorithm.

The trick is to define our algorithm inductively over each Set that can result from Represent. We can then pick different algorithms from the class by changing the specific way of factoring our type.

Letâs consider the case of left scans. I happen to know itâs going to require Functor capabilities, so weâll also define that:

record Functor (F : Set đ â Set đ) : Set (lsuc đ) where field fmap : {A B : Set đ} â (A â B) â F A â F B record LScan (F : Set â Set) : Setâ where field overlap âŚ func âŚ : Functor F lscan : âŚ Monoid A âŚ â F A â F A Ă A open Functor âŚ ... âŚ open LScan âŚ ... âŚ

Whatâs with the type of lscan? This thing is an exclusive scan, so the first element is always mempty, and thus the last elemenet is always returned as projâ of lscan.

We need to implement LScan for each Representation, and because there is no global coherence requirement in Agda, we can define our Functor instances at the same time.

The simplest case is void which we can scan because we have a âĽ in negative position:

instance lV : LScan (\a â âĽ) lV .func .fmap f x = âĽ-elim x lV .lscan ()

â¤ is also trivial. Notice that there isnât any `a`

inside of it, so our final accumulated value must be mempty:

lU : LScan (\a â â¤) lU .func .fmap f x = x lU .lscan x = x , mempty

The identity functor is also trivial. Except this time, we *do* have a result, so it becomes the accumulated value, and we replace it with how much weâve scaned thus far (nothing):

lP : LScan (\a â a) lP .func .fmap f = f lP .lscan x = mempty , x

Coproducts are uninteresting; we merely lift the tag:

l+ : âŚ LScan F âŚ â âŚ LScan G âŚ â LScan (\a â F a â G a) l+ .func .fmap f (injâ y) = injâ (fmap f y) l+ .func .fmap f (injâ y) = injâ (fmap f y) l+ .lscan (injâ x) = let x' , y = lscan x in injâ x' , y l+ .lscan (injâ x) = let x' , y = lscan x in injâ x' , y

And then we come to the interesting cases. To scan the product of `F`

and `G`

, we notice that every left scan of `F`

is a prefix of `F Ă G`

(because `F`

is on the left.) Thus, we can use `lscan F`

directly in the result, and need only adjust the results of `lscan G`

with the accumulated value from `F`

:

l* : âŚ LScan F âŚ â âŚ LScan G âŚ â LScan (\a â F a Ă G a) l* .func .fmap f x .projâ = fmap f (x .projâ) l* .func .fmap f x .projâ = fmap f (x .projâ) l* .lscan (f-in , g-in) = let f-out , f-acc = lscan f-in g-out , g-acc = lscan g-in in (f-out , fmap (f-acc <>_) g-out) , f-acc <> g-acc

l* is what makes the whole algorithm parallel. It says we can scan `F`

and `G`

in parallel, and need only a single join node at the end to stick `f-acc <>_`

on at the end. This parallelism is visible in the `let`

expression, where there is no data dependency between the two bindings.

Our final generic instance of LScan is over composition. Howevef, we canât implement LScan for every composition of functors, since we require the ability to âzipâ two functors together. The paper is pretty cagey about exactly what `Zip`

is, but after some sleuthing, I think itâs this:

record Zip (F : Set â Set) : Setâ where field overlap âŚ func âŚ : Functor F zip : {A B : Set} â F A â F B â F (A Ă B) open Zip âŚ ... âŚ

That looks a lot like being an applicative, but itâs missing `pure`

and has some weird idempotent laws that are not particularly relevant today. We can define some helper functions as well:

zipWith : â {A B C} â âŚ Zip F âŚ â (A â B â C) â F A â F B â F C zipWith f fa fb = fmap (uncurry f) (zip fa fb) unzip : âŚ Functor F âŚ â {A B : Set} â F (A Ă B) â F A Ă F B unzip x = fmap projâ x , fmap projâ x

Armed with all of this, we can give an implementation of lscan over functor composition. The idea is to lscan each inner functor, which gives us an `G (F A Ă A)`

. We can then unzip that, whose second projection is then the totals of each inner scan. If we scan these *totals*, weâll get a running scan for the whole thing; and all thatâs left is to adjust each.

instance lâ : âŚ LScan F âŚ â âŚ LScan G âŚ â âŚ Zip G âŚ â LScan (Compose G F) lâ .func .fmap f = fmap f lâ .lscan (compose gfa) = let gfa' , tots = unzip (fmap lscan gfa) tots' , tot = lscan tots adjustl t = fmap (t <>_) in compose (zipWith adjustl tots' gfa') , tot

And weâre done! We now have an algorithm defined piece-wise over the fundamental ADT building blocks. Letâs put it to use.

Letâs pretend that Vecs are random access arrays. Weâd like to be able to build array algorithms out of our algorithmic building blocks. To that end, we can make a typeclass corresponding to types that are isomorphic to arrays:

open import Data.Nat open import Data.Vec hiding (zip; unzip; zipWith) record ArrayIso (F : Set â Set) : Setâ where field Size : â deserialize : Vec A Size â F A serialize : F A â Vec A Size -- also prove it's an iso open ArrayIso âŚ ... âŚ

There are instances of ArrayIso for the functor building blocks (though none for :+: since arrays are big records.) We can now use an ArrayIso and an LScan to get our desired parallel array algorithms:

genericScan : âŚ Monoid A âŚ â (rep : Rep) â âŚ d : ArrayIso (Represent rep) âŚ â âŚ LScan (Represent rep) âŚ â Vec A (Size âŚ d âŚ) â Vec A (Size âŚ d âŚ) Ă A genericScan _ âŚ d = d âŚ x = let res , a = lscan (deserialize x) in serialize âŚ d âŚ res , a

I think this is the first truly dependent type Iâve ever written. We take a Rep corresponding to how weâd like to divvy up the problem, and then see if the Represent of that has ArrayIso and LScan instances, and then give back an algorithm that scans over arrays of the correct Size.

Finally weâre ready to try this out. We can give the RList implementation from earlier:

âˇ_ : Rep â Rep âˇ_ a = Par :*: a _ : âŚ Monoid A âŚ â Vec A 4 â Vec A 4 Ă A _ = genericScan (âˇ âˇ âˇ Par)

or the LList instance:

_â : Rep â Rep _â a = a :*: Par _ : âŚ Monoid A âŚ â Vec A 4 â Vec A 4 Ă A _ = genericScan (Par â â â)

But we can also come up with more interesting strategies as well. For example, we can divvy up the problem by left-associating the first half, and right-associating the second:

_ : âŚ Monoid A âŚ â Vec A 8 â Vec A 8 Ă A _ = genericScan ((Par â â â) :*: (âˇ âˇ âˇ Par))

This one probably isnât an *efficient* algorithm, but itâs cool that we can express such a thing so succinctly. Probably of more interest is a balanced tree over our array:

_ : âŚ Monoid A âŚ â Vec A 16 â Vec A 16 Ă A _ = let â_â a = a :*: a in genericScan (â â â â Par â â â â)

The balanced tree over products is interesting, but what if we make a balanced tree over *composition?* In essence, we can split the problem into chunks of $2^(2^n)$ amounts of work via Bush:

{-# NO_POSITIVITY_CHECK #-} data Bush : â â Set â Set where twig : A Ă A â Bush 0 A bush : {n : â} â Bush n (Bush n A) â Bush (suc n) A

Which we wonât use directly, but can use itâs Rep:

_ : âŚ Monoid A âŚ â Vec A 16 â Vec A 16 Ă A _ = let pair = Par :*: Par in genericScan ((pair :â: pair) :â: (pair :â: pair))

The paper compares several of these strategies for dividing-and-conquering. In particular, it shows that we can minimize total work via a left-associated â_â strategy, but maximize parallelism with a *right*-associated â_â. And using the `Bush`

from earlier, we can get a nice middle ground.

The paper follows up, applying this approach to implementations of the fast fourier transform. There, the Bush approach gives constant factor improvments for both *work* and *parallelism,* compared to all previously known algorithms.

Results like these are strong evidence that Elliott is *actually onto something* with his seemingly crazy ideas that computation should be elegant and well principled. Giving significant constant factor improvements to well-known, extremely important algorithms *mostly for free* is a true superpower, and is worth taking extremely seriously.

Andrew McKnight and I tried to use this same approach to get a nice algorithm for sorting, hoping that we could get well-known sorting algorithms to fall out as special cases of our more general functor building blocks. We completely failed on this front, namely because we couldnât figure out how to give an instance for product types. Rather alarmingly, weâre not entirely sure *why* the approach failed there; maybe it was just not thinking hard enough.

Another plausible idea is that sorting requires branching, and that this approach only works for statically-known codepaths.

Andrew and I spent a good chunk of the week thinking about this problem, and we figure there are solid odds that you could *automatically* discover these generic algorithmic building blocks from a well-known algorithm. Hereâs the sketch:

Use the well-known algorithm as a specification, instantiate all parameters at small types and see if you can find instances of the algorithm for the functor building blocks that agree with the spec. It seems like you should be able to use factorization of the input to target which instances youâre looking for.

Of course, once you have the algorithmic building blocks, conventional search techniques can be used to optimize any particular goal you might have.

]]>We might as well dive in. Since all of this complexity analysis stuff shouldnât *change* anything at runtime, we really only need to stick the analysis in the types, and can erase it all at runtime.

The paper thus presents its main tools in an `abstract`

block, which is a new Agda feature for me. And wow, does Agda ever feel like itâs Haskell but from the future. An `abstract`

block lets us give some definitions, which *inside* the `abstract`

block can be normalized. But outside the block, they are opaque symbols that are just what they are. This is a delightful contrast to Haskell, where we need to play a game of making a new module, and carefully not exporting things in order to get the same behavior. And even then, in Haskell, we canât give opaque `type`

synonyms or anything like that.

Anyway, the main type in the paper is Thunk, which tracks how many computation steps are necessary to produce an eventual value:

abstract Thunk : â â Set â Set Thunk n a = a

Because none of this exists at runtime, we can just ignore the `n`

argument, and use the `abstract`

ion barrier to ensure nobody can use this fact in anger. Thunk is a *graded* monad, that is, a monad parameterized by a monoid, which uses `mempty`

for `pure`

, and `mappend`

for binding. We can show that Thunk does form a graded monad:

pure : a â Thunk 0 a pure x = x infixl 1 _>>=_ _>>=_ : Thunk m a â (a â Thunk n b) â Thunk (m + n) b x >>= f = f x infixr 1 _=<<_ _=<<_ : (a â Thunk n b) â Thunk m a â Thunk (m + n) b f =<< x = f x

Weâll omit the proofs that Thunk really is a monad, but itâs not hard to see; Thunk is truly just the identity monad.

Thunk is also equipped with two further operations; the ability to mark a computation cycle, and the ability to extract the underlying value by throwing away the complexity analysis:

infixr 0 !_ !_ : Thunk n a â Thunk (suc n) a !_ a = a force : {a : Set} â Thunk n a â a force x = x

Here, !_ is given a low, right-spanning precedence, which means itâs relatively painless to annotate with:

_ : Thunk 3 â _ = ! ! ! pure 0

Our definitions are âopt-in,â in the sense that the compiler wonât yell at you if you forget to call !_ somewhere a computational step happens. Thus, we require users to follow the following conventions:

- Every function body must begin with a call to !_.
- force may not be used in a function body.
- None of pure, _>>=_ nor !_ may be called partially applied.

The first convention ensures we count everything that should be counted. The second ensures we donât cheat by discarding complexity information before itâs been counted. And the third ensures we donât accidentally introduce uncounted computation steps.

The first two are pretty obvious, but the third is a little subtler. Under the hood, partial application gets turned into a lambda, which introduces a computation step to evaluate. But that step wonât be ticked via !_, so we will have lost the bijection between our programs and their analyses.

The paper shows us how to define a lazy vector. VecL `a c n`

is a vector of `n`

elements of type `a`

, where the cost of forcing each subsequent tail is `c`

:

{-# NO_POSITIVITY_CHECK #-} data VecL (a : Set) (c : â) : â â Set where [] : VecL a c 0 _âˇ_ : a â Thunk c (VecL a c n) â VecL a c (suc n) infixr 5 _âˇ_

Letâs try to write fmap for VecL. Weâre going to need a helper function, which delays a computation by artificially inflating its number of steps:

abstract wait : {n : â} â Thunk m a â Thunk (n + m) a wait m = m

(the paper follows its own rules and ensures that we call !_ every time we wait, thus it comes with an extra suc in the type of wait. It gets confusing, so weâll use this version instead.)

Unfortunately, the paper also plays fast and loose with its math. Itâs fine, because the math is right, but the code presented in the paper doesnât typecheck in Agda. As a workaround, we need to enable rewriting:

open import Agda.Builtin.Equality.Rewrite {-# REWRITE +-suc +-identityĘł #-}

Weâll also need to be able to lift equalities over the `Thunk`

time bounds:

cast : m âĄ n â Thunk m a â Thunk n a cast eq x rewrite eq = x

Finally, we can write fmap:

fmap : {c fc : â} â (a â Thunk fc b) â VecL a c n â Thunk (2 + fc) (VecL b (2 + fc + c) n) fmap f [] = wait (pure []) fmap {c = c} f (x âˇ xs) = ! f x >>= \x' â ! pure (x' âˇ cast (+-comm c _) (xs >>= fmap f))

This took me about an hour to write; Iâm not convinced the approach here is as âlightweightâ as claimed. Of particular challenge was figuring out the actual time bounds on this thing. The problem is that we usually reason about asymptotics via Big-O notation, which ignores all of these constant factors. What would be nicer is the hypothetical type:

```
fmap
: {c fc : â}
Thunk (O fc) b)
â (a â VecL a c n
â Thunk (O c) (VecL b (O (fc + c)) n) â
```

where every thunk is now parameterized by `O x`

saying our asymptotics are bounded by `x`

. Weâll see about fleshing this idea out later. For now, we can power through on the paper, and write vector insertion. Letâs assume we have a constant time comparison function for a:

postulate _<=_ : a â a â Thunk 1 Bool

First things first, we need another waiting function to inflate the times on every tail:

waitL : {c' : â} {c : â} â VecL a c' n â Thunk 1 (VecL a (2 + c + c') n) waitL [] = ! pure [] waitL (x âˇ xs) = ! pure (x âˇ wait (waitL =<< xs))

and a helper version of if_then_else_ which accounts in Thunk:

if_then_else_ : Bool â a â a â Thunk 1 a if false then t else f = ! pure f if true then t else f = ! pure t infixr 2 if_then_else_

we can thus write vector insertion:

insert : {c : â} â a â VecL a c n â Thunk 4 (VecL a (4 + c) (suc n)) insert x [] = wait (pure (x âˇ wait (pure []))) insert x (y âˇ ys) = ! x <= y >>= \b â ! if b then x âˇ wait (waitL (y âˇ ys)) else y âˇ (insert x =<< ys)

The obvious followup to insert is insertion sort:

open import Data.Vec using (Vec; []; _âˇ_; tail) sort : Vec a n â Thunk (1 + 5 * n) (VecL a (4 * n) n) sort [] = ! pure [] sort (x âˇ xs) = ! insert x =<< sort xs

This thing looks linear, but insertion sort is $O(n^2)$, so what gives? The thing to notice is that the cost of each *tail* is linear, but we have $O(n)$ tails, so forcing the whole thing indeed works out to $O(n^2)$. We can now show head runs in constant time:

head : {c : â} â VecL a c (suc n) â Thunk 1 a head (x âˇ _) = ! pure x

and that we can find the minimum element in linear time:

minimum : Vec a (suc n) â Thunk (8 + 5 * n) a minimum xs = ! head =<< sort xs

Interestingly, Agda can figure out the bounds on minimum by itself, but not any of our other functions.

The paper goes on to show that we can define last, and then get a quadratic-time `maximum`

using it:

last : {c : â} â VecL a c (suc n) â Thunk (1 + suc n * suc c) a last (x âˇ xs) = ! last' x =<< xs where last' : {c : â} â a â VecL a c n â Thunk (1 + n * suc c) a last' a [] = ! pure a last' _ (x âˇ xs) = ! last' x =<< xs

Trying to define `maximum`

makes Agda spin, probably because of one of my rewrite rules. But hereâs what it should be:

```
maximum : Vec a (suc n) â Thunk (13 + 14 * n + 4 * n ^ 2) a
maximum xs = ! last =<< sort xs
```

The paper goes on to say some thinks about partially evaluating thunks, and then shows its use to measure some popular libraries. But Iâm more interested in making the experience better.

Clearly this is all too much work. When we do complexity analysis by hand, we are primarily concerned with *complexity classes,* not exact numbers of steps. How hard would it be to generalize all of this so that `Thunk`

takes a function bounding the runtime necessary to produce its value?

First, a quick refresher on what big-O means. A function $f : \mathbb{N} \to \mathbb{N}$ is said to be in $O(g)$ for some $g : \mathbb{N} \to \mathbb{N}$ iff:

$\exists (C k : \mathbb{N}). \forall (n : \mathbb{N}, k \leq n). f(n) \leq C \cdot g(n)$

That is, there is some point $k$ at which $g(n)$ stays above $f(n)$. This is the formal definition, but in practice we usually play rather fast and loose with our notation. For example, we say âquicksort is $O(n\cdot\log{n})$ in the length of the listâ, or â$O(n\cdot\log{m})$ , where $m$ is the size of the first argument.â

We need to do a bit of elaboration here to turn these informal statements into a formal claim. In both cases, there should are implicit binders inside the $O(-)$, binding $n$ in the first, and $m, n$ in the second. These functions then get instantiated with the actual sizes of the lists. Itâs a subtle point, but it needs to be kept in mind.

The other question is how the hell do we generalize that definition to multiple variables? Easy! We replace $n : \mathbb{N}, k \leq n$ with a vector of natural numbers, subject to the constraint that theyâre *all* bigger than $k$.

OK, letâs write some code. We can give the definition of O:

open import Data.Vec.Relation.Unary.All using (All; _âˇ_; []) renaming (tail to tailAll) record O {vars : â} (g : Vec â vars â â) : Set where field f : Vec â vars â â C : â k : â def : (n : Vec â vars) â All (k â¤_) n â f n â¤ C * g n

The generality of O is a bit annoying for the common case of being a function over one variable, so we can introduce a helper function O':

hoist : {a b : Set} â (a â b) â Vec a 1 â b hoist f (x âˇ []) = f x O' : (â â â) â Set O' f = O (hoist f)

We can trivially lift any function `f`

into O `f`

:

O-build : {vars : â} â (f : Vec â vars â â) â O f O-build f .O.f = f O-build f .O.C = 1 O-build f .O.k = 0 O-build f .O.def n x = â¤-refl

and also trivially weaken an O into using more variables:

O-weaken : â {vars} {f : Vec â vars â â} â O f â O (f â tail) O-weaken o .O.f = o .O.f â tail O-weaken o .O.C = o .O.C O-weaken o .O.k = o .O.k O-weaken o .O.def (_ âˇ x) (_ âˇ eq) = o .O.def x eq

More interestingly, we can lift a given O' into a higher power, witnessing the fact that eg, something of $O(n^2)$ is also $O(n^3)$:

O-^-suc : {n : â} â O' (_^ n) â O' (_^ suc n) O-^-suc o .O.f = o .O.f O-^-suc o .O.C = o .O.C O-^-suc o .O.k = suc (o .O.k) O-^-suc {n} o .O.def xs@(x âˇ []) ps@(sâ¤s px âˇ []) = begin f xs â¤â¨ def xs (â¤-step px âˇ []) âŠâ¤ C * (x ^ n) â¤â¨ *-monoËĄ-â¤ (x ^ n) (mâ¤m*n C (sâ¤s zâ¤n)) âŠâ¤ (C * x) * (x ^ n) âĄâ¨ *-assoc C x (x ^ n) âŠâĄ C * (x * (x ^ n)) â where open O o open â¤-Reasoning

However, the challenge is and has always been to simplify the construction of Thunk bounds. Thus, weâd like the ability to remove low-order terms from Os. We can do this by eliminating $n^k$ whenever there is a $n^{k'}$ term around with $k \leq k'$:

postulate O-drop-low : {z x y k k' : â} â k â¤ k' â O' (\n â z + x * n ^ k + y * n ^ k') â O' (\n â z + n ^ k')

The `z`

variable here lets us compose O-drop-low terms, by subsequently instantiating

As a special case, we can eliminate constant terms via O-drop-low by first expanding constant terms to be coefficients of $n^0$:

O-drop-1 : {x y k : â} â O' (\n â x + y * n ^ k) â O' (\n â n ^ k) O-drop-1 {x} {y} {k} o rewrite sym (*-identityĘł x) = O-drop-low {0} {x} {y} {k = 0} {k} zâ¤n o

With these functions, we can now easily construct O' values for arbitrary one-variable functions:

_ : O' (_^ 1) _ = O-drop-1 {4} {5} {1} $ O-build $ hoist \n â 4 + 5 * n ^ 1 _ : O' (_^ 2) _ = O-drop-1 {4} {1} {2} $ O-drop-low {4} {5} {3} {1} {2} (sâ¤s zâ¤n) $ O-build $ hoist \n â 4 + 5 * n ^ 1 + 3 * n ^ 2

Finally, we just need to build a version of Thunk that is adequately lifted over the same functions we use for O:

abstract OThunk : {vars : â} â (Vec â vars â â) â Set â Set OThunk _ a = a OThunk' : (â â â) â Set â Set OThunk' f = OThunk (hoist f)

The limit function can be used to lift a Thunk into an OThunk:

limit : {vars : â} {f : Vec â vars â â} {a : Set} â (v : Vec â vars) â (o : O f) â Thunk (o .O.f v) a â OThunk f a limit _ _ x = x

and we can now give an asymptotic bound over sort:

```
: O' (_^ 1)
o2 = O-drop-1 {1} {5} {1} $ O-build $ hoist \n -> 1 + 5 * n
o2
: Vec a n â OThunk' (_^ 1) (VecL a (4 * n) n)
linearHeadSort = n} v = limit (n âˇ []) o2 $ sort v linearHeadSort {n
```

Iâm traveling right now, and ran out of internet on publication day, which means I donât have a copy of the paper in front of me as I write this (foolish!) Overall, the paper is slightly interesting, though I donât think thereâs anything especially novel here. Sticking the runtime behavior into the type is pretty much babbyâs first example of graded monads, and we donât even get asymptotics out of it! Instead we need to push big polynomials around, and explicitly call wait to make different branches work out.

The O stuff Iâve presented here alleviates a few of those problems; as it allows us to relatively-easily throw away the polynomials and just work with the highest order terms. A probably better approach would be to throw away the functions, and use a canonical normalizing-form to express the asymptotes. Then we could define a $\lub$ operator over OThunks, and define:

`>>=_ : OThunk f a â (a â OThunk g b) â OThunk (f â g) b _`

to let us work compositionally in the land of big O.

My biggest takeaway here is that the techniques described in this paper are probably not powerful enough to be used in anger. Or, at least, not if you actually want to get any work done. Between the monads, polynomials, and waiting, the experience could use a lot of TLC.

]]>A while back I reviewed some paper (maybe codata? â too lazy to check) and came away thinking âI should learn more about presheaves.â The first paper I found is A Very Elementary Introduction to Sheaves by Mark Agrios, and mildly interestingly, was published less than three weeks ago.

The paper is called âvery elementary,â and in the first sentence states it âis a very non-rigorous, loose, and extremely basic introduction to sheaves,â and it delivers on these promises. There is a section on metaphorically what a sheaf is, and then two somewhat-worked examples.

After reading through the paper, I feel like I have a very rough idea of what a sheaf is, and thought that this would be an excellent opportunity to flex my category theory muscles. That is, can I correctly generalize from these two examples to a solid category theoretical definition of a sheaf? Iâm not sure, but this is a unique opportunity, so itâs worth a shot.

The central metaphor of the paper is that a sheaf enriches some mathematical structure, much like a garden enriches a plot of dirt. There are lots of gardens you could make on a plot of dirt, and then you can harvest things from them. I guess this makes sense to the author, but it doesnât particularly help me. I suspect this is an example of the monad tutorial fallacy in the wild: after thinking really hard about an idea for a while, the author came up with a metaphor that really works for them. But, this metaphor is more an artifact of their thinking process than it is descriptive of the idea itself. Anyway, either way, I wasnât able to extract much meaning here.

We can build a (pre-?)sheaf over a graph. By playing fast and loose with our types like mathematicians are so wont to do, we can model the edge $e_{ij} : V_i \to V_j$ in a graph as an âintersection of the nodes it connects.â The paper writes $e_{ij} < v_i, v_j$. Iâm not super sure what that means, but I think itâs saying that given some graph $G = (V, E)$, we can say $e_{ij} \subseteq v_i \cup v_j$? Except that this doesnât typecheck, since `v_i`

is an element of a set, not a set itself. I donât know.

Anyway, the important thing here seems to be that there is a preorder between edges and vertices. So letâs quickly define a `Preorder`

:

record Preorder : Set where field Carrier : Set _<_ : Carrier â Carrier â Set <-refl : (a : Carrier) â a < a <-trans : {a b c : Carrier} â a < b â b < c â a < c

and then just forget about the whole graph thing, because I am not convinced it is a meaningful presentation. Instead, weâll cheat, and just build exactly the object we want to discuss.

data Ex : Set where v1 : Ex v2 : Ex e12 : Ex

corresponding to this rather boring graph:

We can then build a Preorder on Ex with explicit cases for e12 being less than its vertices:

data Ex< : Ex â Ex â Set where e12<v1 : Ex< e12 v1 e12<v2 : Ex< e12 v2

and two cases to satisfy the preorder laws:

ex<-refl : (x : Ex) â Ex< x x

and then mechanically hook everything up:

module _ where open Preorder ex-preorder : Preorder ex-preorder .Carrier = Ex ex-preorder ._<_ = Ex< ex-preorder .<-refl = ex<-refl ex-preorder .<-trans e12<v1 (ex<-refl .v1) = e12<v1 ex-preorder .<-trans e12<v2 (ex<-refl .v2) = e12<v2 ex-preorder .<-trans (ex<-refl _) e12<v1 = e12<v1 ex-preorder .<-trans (ex<-refl _) e12<v2 = e12<v2 ex-preorder .<-trans (ex<-refl x) (ex<-refl _) = ex<-refl x

The paper goes on to say we have some sheaf `F`

, which maps Exs to âjust about anything,â this codomain being called the *stalk.* For now, letâs assume itâs to Set.

Furthermore, the sheaf `F`

also has a âsecond mechanism,â which in our example maps an edge $e_{ij} : v_i \to v_j$ to two functions:

$F_{v_i;e_{ij}} : F(v_i) \to F(e_{ij}) \\ F_{v_j;e_{ij}} : F(v_j) \to F(e_{ij})$

This is where some of the frustration in only being given examples comes in. Why are these in the definition of a sheaf? The only thing that could possibly make any sense to me is that this comes from a more general construction:

`restrict : (x y : Ex) â x < y â Stalk y â Stalk x`

which states we have a mapping from $F(y)$ to $F(x)$ if and only if we have $x < y$. These `restrict`

things are called *restriction maps*.

Whatâs further confusing is the following point:

Since each stalk is a vector space, it is natural to have our restriction maps be linear transformations described by matrices.

Why linear transformations, and not just arbitrary functions? When I hear âlinear transformationâ I think homomorphism, or more probably, morphism in some category. Which then probably means the `Stalk`

isnât a function to Set, itâs a mapping into a category.

OK, so that all seems straightforward enough. Letâs try to formalize it.

module Sheaf (pre : Preorder) (C : Category) where open Preorder pre open Category C record Sheaf : Set where field Stalk : Carrier â Obj restrict : {x y : Carrier} â x < y â Stalk y ~> Stalk x

which seems reasonable. The paper now gives us a specific sheaf, with restrict e12<v1 being the linear map encoded by the matrix:

$\begin{bmatrix} 1 & -1 \\ 0 & 2 \end{bmatrix}$

which we can write as a morphism in LIN (the category of linear algebra, with objects as vector spaces, and morphisms as linear maps):

e12~>v1 : 2 ~> 2 e12~>v1 .linmap (x âˇ y âˇ []) = (x - y) âˇ (+ 2 * y) âˇ [] e12~>v1 .preserves-+ u v = trustMe e12~>v1 .preserves-* a v = trustMe

and restrict e12<v2 being the linear map encoded by the matrix:

$\begin{bmatrix} 3 & 1 & -1 \\ 2 & 0 & 2 \end{bmatrix}$

written as:

e12~>v2 : 3 ~> 2 e12~>v2 .linmap (x âˇ y âˇ z âˇ []) = (+ 3 * x + y - z) âˇ (+ 2 * x + + 2 * z) âˇ [] e12~>v2 .preserves-+ u v = trustMe e12~>v2 .preserves-* a v = trustMe

Thus, we can finally build the example `Sheaf`

:

ex : Sheaf ex .Stalk v1 = 2 ex .Stalk v2 = 3 ex .Stalk e12 = 2 ex .restrict e12<v1 = e12~>v1 ex .restrict e12<v2 = e12~>v2 ex .restrict (ex<-refl z) = id

Whatâs with the Stalk of v1 being 2, you might ask? Remember, the stalk is an object in some category, in this case LIN. Objects in LIN are natural numbers, corresponding to the length of vectors.

Hereâs where our categorical generalization of the paper goes a bit haywire. The paper defines a *section* as picking an element from each Stalk of the sheaf. He picks, for example:

$\begin{bmatrix} 2 \\ 1 \end{bmatrix} \in \text{Stalk } v1$

$\begin{bmatrix} 3 \\ -1 \\ 0 \end{bmatrix} \in \text{Stalk } v2$

and

$\begin{bmatrix} 1 \\ -1 \end{bmatrix} \in \text{Stalk } e12$

which is all fine and dandy, except that when we categorize, our objects no longer have internal structure. Fortunately, we can use âgeneralized elements,â a.k.a., morphisms out of the terminal object.

Section : Carrier â Set Section c = terminal ~> Stalk c

That is, a Section is a mapping from every element in the Preorder to a generalized element of its Stalk. We can evaluate a Section by checking the commutativity of all restricts. That is, weâd like the following diagram to commute:

Doing this in Agda is hard because it wants lots of dumb arithmetic proofs, so instead weâll make ourselves content with some by-hand math:

$r \circ S v1 = \begin{bmatrix} 1 & -1 \\ 0 & 2 \end{bmatrix} \begin{bmatrix} 2 \\ 1 \end{bmatrix} = \begin{bmatrix} 1 \\ 2 \end{bmatrix} \neq \begin{bmatrix} 1 \\ -1 \end{bmatrix}$

So, our chosen Section doesnât commute. That is, it doesnât respect the global equalities, thus it is not a *global section.* Sounds like something worth formalizing:

record GlobalSection : Set where field section : forall (c : Carrier) â Section c commutes : {x y : Carrier} â (x<y : x < y) â restrict x<y â section y â section x

All thatâs left is to find a GlobalSection of our weird graph category:

Unfortunately, this formalization doesnât quite work out; there are no interesting arrows out of terminal:

boring-arrows : (f : 0 ~> 1) â (x : Vec â¤ 0) â f .linmap x âĄ + 0 âˇ [] boring-arrows f [] with f .linmap [] in eq ... | x âˇ [] rewrite sym eq = begin f .linmap [] âĄâ¨âŠ f .linmap (map (+ 0 *_) []) âĄâ¨ f .preserves-* (+ 0) _ âŠâĄ map (+ 0 *_) (f .linmap []) âĄâ¨ cong (map (+ 0 *_)) eq âŠâĄ map (+ 0 *_) (x âˇ []) âĄâ¨âŠ (+ 0 * x) âˇ [] âĄâ¨ cong (_âˇ []) (*-zeroËĄ +0) âŠâĄ +0 âˇ [] â where open Eq.âĄ-Reasoning

So, thatâs no good. Weâve modeled Section incorrectly, as the generalized element approach doesnât work, since we are unable to follow the example.

What are some other ways to go from an Obj to a Set? Maybe we could try modeling this as a functor to SET instead:

ex-func : LIN => SET ex-func .F-Obj x = Vec â¤ x ex-func .F-map f = f .linmap ex-func .F-map-id _ _ = refl ex-func .F-map-â g f a = refl

And we can try again with `Section`

s:

and then we can say a `Section`

is an element of the action of Func:

Section : Carrier â Set Section c = F-Obj (Stalk c)

and a `GlobalSection`

, which recall, is a globally-coherent assignment of sections:

record GlobalSection : Set where field section : forall (c : Carrier) â Section c commutes : {x y : Carrier} â (x<y : x < y) â F-map (restrict x<y) (section y) âĄ section x

soln : GlobalSection soln .section v1 = + 2 âˇ + 1 âˇ [] soln .section v2 = -[1+ 1 ] âˇ + 10 âˇ + 3 âˇ [] soln .section e12 = + 1 âˇ + 2 âˇ [] soln .commutes e12<v1 = refl soln .commutes e12<v2 = refl soln .commutes (ex<-refl _) = refl

Sure enough, this was a global section:

$\begin{bmatrix} 2 \\ 1 \end{bmatrix} \in \text{Stalk } v1$

$\begin{bmatrix} -2 \\ 10 \\ 3 \end{bmatrix} \in \text{Stalk } v2$

and

$\begin{bmatrix} 1 \\ 2 \end{bmatrix} \in \text{Stalk } e12$

The paper presents a second example as well. Maybe itâs just that Iâm less well-versed in the subject matter, but this example feels significantly more incoherent than the first. I tried to work through it, and the formalization above was sufficiently powerful to do what I needed, but I didnât understand the example or what it was trying to accomplish. There was some Abelian group stuff that never actually got used.

Rather than clean this section up, Iâm instead going to spend the time before my publication deadline writing about what I learned about pre-sheafs after hitting the wall, and asking for help.

So letâs talk about what all of this sheaf business above is trying to do. The ever helpful Reed Mullanix came to my rescue with a few helpful intuitions. To paraphrase him (if there are any mistakes in the following, they are my mistakes, not his):

Think about a sensor network. You have some physical space, with a series of sensors attached in specific places. Maybe you have a microphone in the hallway, and a camera at the front door, and a thermometer in the bedroom. Each of these sensors is

locally correct, that is, we can be reasonably sure that if the thermometer says 37C, it is in fact 37C.A presheaf is a mapping from this collection of sensors to a world in which we can reason about the total space. For example, we might want to get an idea of whatâs going on in the basement, where we have no sensors, but which is part of our house nevertheless.

And a global section over that presheaf is a globally consistent take on the system. Itâs some mapping into the hypothesis space that

agrees with all of the measurements.If we know itâs 37C in the bedroom, weâre probably not going to see snow in the front-door camera.

Okay, so whatâs all this preorder stuff about? I think itâs actually just a poor manâs category. We can lift any preorder into a category by considering the `<`

relationship to be a morphism:

module PreorderToCategory (P : Preorder) where open Preorder P open Category open import Data.Unit using (â¤; tt) cat : Category cat .Obj = Carrier cat ._~>_ = _<_ cat ._â_ f g = â¤ cat .â-equiv = sorry cat .id {A = A} = <-refl A cat ._â_ g f = <-trans f g cat .â-cong = Îť _ _ â tt cat .id-r f = tt cat .id-l f = tt cat .â-assoc h g f = tt

and now that we have a Category, we can avoid the whole Sheaf / GlobalSection by giving a functor into SET. Well, almost, because restrict goes the opposite direction. So instead, we can build an opposite category:

module Op (C : Category) where open Category data OpArr : Obj C â Obj C â Set where reverse : {X Y : Obj C} â C [ X , Y ] â OpArr Y X op : Category op .Obj = C .Obj op ._~>_ = OpArr op ._â_ (reverse f) (reverse g) = C ._â_ f g op .â-equiv {A} {B} = sorry op .id = reverse (C .id) op ._â_ (reverse g) (reverse f) = reverse (C ._â_ f g) op .â-cong = sorry op .id-r (reverse f) = C .id-l f op .id-l (reverse f) = C .id-r f op .â-assoc (reverse h) (reverse g) (reverse f) = setoid C .isEquivalence .S.IsEquivalence.sym (C .â-assoc f g h) where open import Relation.Binary.Bundles using (Setoid) open Setoid using (isEquivalence) import Relation.Binary.Structures as S

Now, we can express a presheaf as a functor:

module _ where open import Category.MyFunctor open Op Presheaf : Category â Set Presheaf C = op C => SET

or our specific example from earlier:

module _ where open PreorderToCategory ex-preorder open _=>_ open import Data.Nat using (â) open Op Z : â â Set Z = Vec â¤ ex' : Presheaf cat ex' .F-Obj v1 = Z 2 ex' .F-Obj v2 = Z 3 ex' .F-Obj e12 = Z 2 ex' .F-map (reverse e12<v1) = e12~>v1 .linmap ex' .F-map (reverse e12<v2) = e12~>v2 .linmap ex' .F-map (reverse (ex<-refl _)) a = a ex' .F-map-id A a = refl ex' .F-map-â (reverse e12<v1) (reverse (ex<-refl _)) a = refl ex' .F-map-â (reverse e12<v2) (reverse (ex<-refl _)) a = refl ex' .F-map-â (reverse (ex<-refl _)) (reverse e12<v1) a = refl ex' .F-map-â (reverse (ex<-refl _)) (reverse e12<v2) a = refl ex' .F-map-â (reverse (ex<-refl _)) (reverse (ex<-refl _)) a = refl

which leaves only the question of what a `GlobalSection`

is under this representation.

I got stumped on this one for a while too, but again, Reed to the rescue, who points out that in our preorder, `<`

corresponds to a âsmallerâ space. Thus, we want to find a mapping out of the biggest space, which corresponds to a top element in the order, or a terminal object in the category. The terminal object is going to be the âtotal spaceâ in consideration (in our sensor example, eg.) and the functor laws will ensure consistency.

GlobalSection : {C : Category} â (pre : Presheaf C) â (t : HasTerminal C) â Set GlobalSection pre t = pre ._=>_.F-Obj (t .HasTerminal.terminal)

Unfortunately, this is a problem for our worked example â we donât *have* a terminal object! But thatâs OK, itâs easy to trivially construct one by just adding a top:

and by picking an object in SET to map it to for our presheaf. There are some interesting choices here; we could just pick â¤, which is interesting in how boring a choice it is. Such a thing trivially satisfies all of the requirements, but it doesnât tell us much about the world. This is the metaphorical equivalent of explaining our sensorsâ readings as âanything is possible!â

More interestingly, we could pick `F-Obj terminal`

to be $\mathbb{Z}^2 Ă \mathbb{Z}^3 Ă \mathbb{Z}^2$, corresponding to the product of `F-Obj v1`

, `F-Obj v2`

and `F-Obj e12`

. We can satisfy the functor laws by projecting from the `F-Obj term`

down to one of its components. And, best of all, it gives us a place to stick the values from our worked example.

Iâd love to code this up in more detail, but unfortunately Iâm out of time. Thatâs the flaw of trying to get through one paper a week, the deadline is strict whether youâre ready for it or not.

This whole post is a literate Agda file.

]]>`(a + b) * (a + b) = a^2 + 2*a*b + b^2`

, which is rather amazing if you think about it. I got curious about how this is possible, and came across AaEIPEiA, quickly skimmed it for the rough approach, and then decided to write my own ring solver. As a result, this post is certainly inspired by AaEIPiA, but my implementation is extremely naive compared to the one presented in the paper. Kidneyâs paper is very good, and I apologize for not doing it justice here.
So, some background. Agda lets you write types that correspond to equalities, and values of those types are proofs of those equalities. For example, we can write the following type:

`(x : â) â (x + 1) * (x + 1) âĄ (x * x) + (1 + 1) * x + 1`

You probably wouldnât write this for its own sake, but it might come up as a lemma of something else youâre trying to prove. However, actually proving this equality is a huge amount of busywork, that takes forever, and isnât actually interesting because we all know that this equality holds. For example, the proof might look something like this:

```
begin(x + 1) * (x + 1)
(x + 1) x 1 âŠ
âĄâ¨ *-+-distrib (x + 1) * x + (x + 1) * 1
(\Ď -> ((x + 1) * x + Ď)) $ *-1-id-r (x + 1) âŠ
âĄâ¨ cong (x + 1) * x + (x + 1)
(\Ď -> Ď + (x + 1)) $ *-comm (x + 1) x âŠ
âĄâ¨ cong (x + 1) + (x + 1)
x * (\Ď -> Ď + (x + 1)) $ *-+-distrib x x 1 âŠ
âĄâ¨ cong (x * x + x * 1) + (x + 1)
âĄâ¨ ? âŠ-- kill me
âĄâ¨ ? âŠ(x * x) + (1 + 1) * x + 1
â
```

Itâs SO MUCH WORK to do *nothing!* This is not an interesting proof! A ring solver lets us reduce the above proof to:

```
begin(x + 1) * (x + 1)
âĄâ¨ solve âŠ(x * x) + (1 + 1) * x + 1
â
```

or, even more tersely:

` solve`

So thatâs the goal here. Automate stupid, boring proofs so that we as humans can focus on the interesting bits of the problem.

Why is this called a ring solver? I donât exactly know, but a ring is some math thing. My guess is that itâs the abstract version of an algebra containing addition and multiplication, with all the usual rules.

And looking at it, sure enough! A ring is a set with two monoids on it, one corresponding to addition, and the other to multiplication. Importantly, we require that multiplication distributes over addition.

Rings technically have additive inverses, but I didnât end up implementing (or needing them.) However, I did require commutativity of both addition and multiplication â more on this later.

The ring laws mean that algebra works in the way we expect arithmetic to work. We can shuffle things around, and probably all have enough experience solving these sorts of problems with pen and paper. But whatâs the actual algorithm here?

At first blush, this sounds like a hard problem! It feels like we need to see if thereâs a way to turn some arbitrary expression into some other arbitrary expression. And that is indeed true, but itâs made easier when you realize that polynomials have a normal form as a sum of products of descending powers. For example, this is in normal form:

`5*x^2 - 3*x + 0`

The problem thus simplifies to determining if two expressions have the same normal form. Thus, we can construct a proof that each expression is equal to its normal form, and then compose those proofs together to show the unnormalized forms are equal.

My implementation is naive, and only works for expressions with a single variable, but I think the approach generalizes if you can find a suitable normal form for multiple variables.

All of this sounds like a good tack, but the hard part is convincing ourselves (and perhaps more importantly, Agda,) that the stated relationship holds. As it happens, we require three equivalent types:

`A`

, the ring weâre actually trying to solve`Poly`

, a syntactic representation of the ring operations`Horner`

, the type of`A`

-normal forms

`Poly`

and `Horner`

are indexed by `A`

, but Iâve left that out for presentation purposes. Furthermore, theyâre also both indexed by the degree of the polynomial, that is, the biggest power they contain. Iâm not sure this was necessary, but it helped me make sure my math was right when I was figuring out how to multiply `Horner`

s.

At a high level, solving a ring equality is really a statement about how `A`

is related to `Poly`

and `Horner`

. We can construct an A-expression by substituting an `A`

for all the variables in a `Poly`

:

`: {n : â} â Poly n â A â A construct `

and we can normalize any syntactic expression:

`: {n : â} â Poly n â Horner n normalize `

thus we can solve a ring equation by hoisting a proof of equality of its normal forms into a proof of equality of its construction:

```
solve: {n : â}
â (x y : Poly n)
â normalize x âĄ normalize y
â (a : A)
â construct x a âĄ construct y a
```

This approach is a bit underwhelming, since we need to explicitly construct syntactic objects (in `Poly`

) corresponding to the expressions weâre trying to solve (in `A`

). But this is something we can solve with Agdaâs macro system, by creating the `Poly`

s by inspecting the actual AST, so weâll consider the approach good enough. Todayâs post is about understanding how to do ring solving, not about how to engineer a nice user-facing interface.

The actual implementation of `solve`

is entirely straight-forward:

```
=
solve x y eq a
begin
construct x a âĄâ¨ construct-is-normal x a âŠ(normalize x) a âĄâ¨ cong (\Ď â evaluate Ď a) eq âŠ
evaluate (normalize y) a âĄâ¨ sym $ construct-is-normal y a âŠ
evaluate
construct y a â
```

given a lemma that `construct`

is equal to evaluating the normal form:

```
construct-is-normal: {N : â}
â (x : Poly N)
â (a : A)
â construct x a âĄ evaluate (normalize x) a
```

The implementation of this is pretty straightforward too, requiring only that we have `+`

and `*`

homomorphisms between `Horner`

and `A`

:

```
+A-+H-homo: â {m n} j k a
â evaluate {m} j a +A evaluate {n} k a âĄ evaluate (j +H k) a
*A-*H-homo: â {m n} j k a
â evaluate {m} j a *A evaluate {n} k a âĄ evaluate (j *H k) a
```

These two lemmas turn out to be the hard part.

Before we get into all of that, letâs first discuss what each of the types looks like. We have `Poly`

, which again, is an initial encoding of the ring algebra:

```
data Poly : â â Set where
: A â Poly 0
con : Poly 1
var _:+_ : {m n : â} â Poly m â Poly n â Poly (m â n)
_:*_ : {m n : â} â Poly m â Poly n â Poly (m + n)
```

We can reify the meaning of `Poly`

by giving a transformation into `A`

:

```
: {N : â} â Poly N â A â A
construct (con x) a = x
construct = a
construct var a (p :+ p2) a = construct p a +A construct p2 a
construct (p :* p2) a = construct p a *A construct p2 a construct
```

Our other core type is `Horner`

, which is an encoding of the Horner normal form of a polynomial:

```
data Horner : â â Set where
: A â Horner 0
PC : {n : â} â A â Horner n â Horner (suc n) PX
```

`Horner`

requires some discussion. Horner normal form isnât the same normal form presented earlier, instead, itâs a chain of linear multiplications. For example, we earlier saw this:

`5*x^2 - 3*x + 0`

in Horner normal form, this would be written as

`0 + x * (3 + x * 5)`

The idea is we can write any polynomial inductively by nesting the bigger terms as sums inside of multiplications against `x`

. We can encode the above as a `Horner`

like this:

`0 (PX 3 (PC 5)) PX `

and then reify the meaning of `Horner`

with respect to `A`

via `evaluate`

:

```
: {n : â} â Horner n â A â A
evaluate (PC x) v = x
evaluate (PX x xs) v = x +A (v *A evaluate xs v) evaluate
```

We can define addition over `Horner`

terms, which is essentially `zipWith (+A)`

:

```
_+H_ : {m n : â} â Horner m â Horner n â Horner (m â n)
_+H_ (PC x) (PC y) = PC (x +A y)
_+H_ (PC x) (PX y ys) = PX (x +A y) ys
_+H_ (PX x xs) (PC y) = PX (x +A y) xs
_+H_ (PX x xs) (PX y ys) = PX (x +A y) (xs +H ys)
```

We can also implement scalar transformations over `Horner`

, which is exactly a monomorphic `fmap`

:

```
: {m : â} â (A â A) â Horner m â Horner m
scalMapHorner (PC x) = PC (f x)
scalMapHorner f (PX x xs) = PX (f x) (scalMapHorner f xs) scalMapHorner f
```

and finally, we can define multiplication over `Horner`

terms:

```
_*H_ : {m n : â} â Horner m â Horner n â Horner (m + n)
_*H_ (PC x) y = scalMapHorner (x *A_) y
_*H_ (PX {m} x xs) (PC y) = scalMapHorner (_*A y) (PX x xs)
_*H_ (PX {m} x xs) yy =
(x *A_) yy +H PX #0 (xs *H yy) scalMapHorner
```

The first two cases here are straightforward, just `scalMapHorner`

-multiply in the constant value and go on your way. The `PX-PX`

case is rather complicated however, but corresponds to the `*-+-distrib`

law:

`*-+-distrib : â x xs yy â (x + xs) * yy âĄ x * yy +A xs * yy`

We take advantage of the fact that we know `x`

is a scalar, by immediately multiplying it in via `scalMapHorner`

.

As alluded to earlier, all thatâs left is to show `evaluate`

-homomorphisms for `+H`

/`+A`

and `*H`

/`*A`

:

```
+A-+H-homo: â {m n} j k a
â evaluate {m} j a +A evaluate {n} k a âĄ evaluate (j +H k) a
*A-*H-homo: â {m n} j k a
â evaluate {m} j a *A evaluate {n} k a âĄ evaluate (j *H k) a
```

Thereâs nothing interesting in these proofs, itâs just three hundred ironic lines of tedious, boring proofs, of the sort that we are trying to automate away.

Given these, we can implement `construct-is-normal`

```
construct-is-normal: {N : â}
â (x : Poly N)
â (a : A)
â construct x a âĄ evaluate (normalize x) a
(con x) a = refl
construct-is-normal = refl
construct-is-normal var a (x :+ y) a
construct-is-normal rewrite construct-is-normal x a
| construct-is-normal y a
| +A-+H-homo (normalize x) (normalize y) a
= refl
(x :* y) a
construct-is-normal rewrite construct-is-normal x a
| construct-is-normal y a
| *A-*H-homo (normalize x) (normalize y) a
= refl
```

Nice!

The homomorphism proofs are left as an exercise to the reader, or you can go look at the code if you want to skip doing it.

My implementation isnât 100% complete, I still need to prove that `*H`

is commutative:

`: â j k â j *H k âĄ k *H j *H-comm `

which shouldnât be hard, because it *is* commutative. Unfortunately, Agda has gone into hysterics, and wonât even typecheck the type of `*H-comm`

, because it canât figure out that `m + n = n + m`

(the implicit indices on the result of `*H`

). As far as I can tell, there is no easy fix here; thereâs some weird `cong`

-like thing for types called `subst`

, but it seems to infect a program and push these weird-ass constraints everywhere.

This is extremely frustrating, because itâs literally the last thing to prove after 300 grueling lines of proof. And itâs also true and isnât even hard to show. Itâs just that I canât get Agda to accept the type of the proof because itâs an idiot that doesnât know about additive commutativity. After a few hours of fighting with getting this thing to typecheck, I just said fuck it and postulated `*H-comm`

.

Stupid Agda.

If you know what Iâve done wrong to deserve this sort of hell, please let me know. It would be nice to be able to avoid problems like this in the future, or resolve them with great ease.

So, thatâs it! Modulo a postulate, weâve managed to implement a ring-solver by showing the equivalence of three different representations of the same data. Just to convince ourselves that it works:

```
: Poly 2
test-a = (var :+ con #1) :* (var :+ con #1)
test-a
: Poly 2
test-b = var :* var :+ two :* var :+ con #1
test-b where
= con #1 :+ con #1
two
success: (x : A)
â (x +A #1) *A (x +A #1) âĄ (x *A x) +A (#1 +A #1) *A x +A #1
= solve test-a test-b refl x success x
```

which Agda happily accepts!

I donât exactly know offhand how to generalize this to multivariate polynomials, but I think the trick is to just find a normal form for them.

As usual, the code for this post is available on Github.

]]>So anyway, today weâre looking at codata. Whatâs that? Essentially, lazy records. By virtue of being lazy, Haskell makes the differentiation between data and codata rather hard to spot. The claim is that functional languages are big on data, object-oriented languages really like codata, and that everything you can do with one can be emulated by the other, which is useful if youâd like to compile FP to OOP, or vice versa.

Codata, like the name implies, have a lot of duals with regular olâ data. The paper introduces a bunch of parallels between the two:

Data | Codata |
---|---|

Concerned with construction | Concerned with destruction |

Define the types of constructors | Define the types of destructors |

Directly observable | Observable only via their interface |

Common in FP | Common in OOP |

Initial algebras | Terminal coalgebras |

Algebraic data structures | Abstract data structures |

`data` |
`class` |

The paperâs claim is that codata is a very useful tool for doing real-world work, and that we are doing ourselves a disservice by not making it first-class:

While codata types can be seen in the shadows behind many examples of programmingâoften hand-compiled away by the programmerânot many functional languages have support for them.

Thatâs a particularly interesting claim; that weâre all already using codata, but itâs hidden away inside of an idiom rather than being a first-class citizen. Iâm always excited to see the ghosts behind the patterns I am already using.

The paper gives a big list of codata that weâre all already using without knowing it:

Instead of writing

```
data Bool where
True :: Bool
False :: Bool
```

I can instead do the usual Church encoding:

```
Bool where
codata if :: Bool -> a -> a -> a
```

which I might express more naturally in Haskell via:

```
ifThenElse :: Bool -> a -> a -> a
True t _ = t
ifThenElse False _ f = f ifThenElse
```

(I suspect this is that âhand-compiling awayâ that the authors were talking about)

However, in the codata presentation, I can recover `true`

and `false`

by building specific objects that fiddle with their arguments just right (using copatterns from a few weeks ago):

```
True : Bool
if True t _ = t
False : Bool
if False _ f = f
```

Thatâs neat, I guess!

As a follow-up, we can try talking about `Tree`

s. Rather than the usual `data`

definition:

```
data Tree t where
Leaf :: t -> Tree t
Branch :: Tree t -> Tree t -> Tree t
walk :: (t -> a) -> (a -> a -> a) -> Tree t -> a
```

we can do it in codata:

```
Tree t where
codata walk :: Tree t -> (t -> a) -> (a -> a -> a) -> a
```

and reconstruct the âconstructors:â

```
Leaf x :: t -> Tree t
Leaf t) mk _ = mk t
walk (
Branch :: Tree t -> Tree t -> Tree t
Branch l r) mk comb = comb (walk l mk comb) (walk r mk comb) walk (
```

The presentation in the paper hand-compiles `Tree!data`

into two declarations:

```
TreeVisitor t a where
codata visitLeaf :: TreeVisitor t a -> t -> a
{ visitBranch :: TreeVisitor t a -> a -> a -> a
,
}
Tree t where
codata walk :: Tree t -> TreeVisitor t a -> a
```

which is the same thing, but with better named destructors.

You know the problem. Youâre programming some search, and want to have a stopping depth. Maybe youâre writing a chessai and donât want to wait until the ends of time for the search to finish. Easy enough, right? Just add an integer that counts down whenever you recurse:

```
search :: Int -> Position -> [Position]
0 _ = []
search = -- do lots of work search n as
```

So you set `n`

to something that seems reasonable, and get your moves back. But then you realize you had more time to kill, so youâd like to resume the search where you left off. But thereâs no good way to do this, and starting back from the beginning would involve wasting a lot of effort. You can certainly program around it, but again, itâs hand-compiling away codata.

Instead, we can express the problem differently:

```
Rose a where
codata node :: Rose a -> a
{ children :: Rose a -> [Rose a]
, }
```

Recall that codata is built-in lazy, so by repeatedly following `children`

we can further explore the tree state. In OOP I guess weâd call this a generator or an iterator or something. Probably a factory of some sort.

But once we have `Rose`

we can implement pruning:

```
prune :: Int -> Rose Position -> Rose Position
= node t
node (prune n t) 0 t) = []
children (prune = fmap (prune (n - 1)) $ children t children (prune n t)
```

I *really* like copattern matching.

You know how we have extentional and intentional definitions for sets? Like, compare:

```
newtype Set a = Set { unSet :: [a] }
lookup :: Set a -> a -> Bool
lookup s t = elem t $ unset s
```

vs

`newtype Set a = Set { lookup :: a -> Bool }`

That latter version is the Church-encoded version. Instead we can give an interface for both sorts of sets as codata, defined by their *interface* as sets. This is everyday OOP stuff, but a little weird in FP land:

```
Set a where
codata isEmpty :: Set a -> Bool
{ lookup :: Set a -> a -> Bool
, insert :: Set a -> a -> Set a
, union :: Set a -> Set a -> Set a
, }
```

My dudes this is just an interface for how you might want to interact with a Set. We can implement the listy version from above:

```
listySet :: [a] -> Set a
= null ls
isEmpty (listySet ls) lookup (listySet ls) a = elem a ls
= listSet (a : ls)
insert (listySet ls) a = foldr insert s ls union (listySet ls) s
```

but we can also implement an infinitely big set akin to our functiony-version:

```
evensUnion :: Set Int -> Set Int
= False
isEmpty (evensUnion s) lookup (evensUnion s) a = mod a 2 == 0 || lookup a s
= evensUnion $ insert s a
insert (evensUnion s) a = evensUnion $ union s s' union (evensUnion s) s'
```

This thing is a little odd, but `evensUnion`

is the set of the even numbers unioned with some other set. The built-in unioning is necessary to be able to extend this thing. Maybe we might call it a decorator pattern in OOP land?

One last example, using type indices to represent the state of something. The paper gives sockets:

```
data State = Raw | Bound | Live
type Socket :: State -> Type
Socket i where
codata bind :: Socket 'Raw -> String -> Socket 'Bound
{ connect :: Socket 'Bound -> Socket 'Live
, send :: Socket 'Live -> String -> ()
, recv :: Socket 'Live -> String
, close :: Socket 'Live -> ()
, }
```

The type indices here ensure that weâve bound the socket before connecting to it, and connected to it before we can send or receive.

Contrast this against what we can do with GADTs, which tell us how something was built, not how it can be used.

Unsurprisingly, data and codata are two sides of the same coin: we can compile one to the other and vice versa.

Going from data to codata is giving a final encoding for the thing; as weâve seen, this corresponds to the Boehm-Berarducci encoding. The trick is to replace the type with a function. Each data constructor corresponds to an argument of the function, the type of which is another function that returns `a`

, and as arguments takes each argument to the data constructor. To tie the knot, replace the recursive bits with `a`

.

Letâs take a look at a common type:

`data List a = Nil | Cons a (List a)`

We will encode this as a function, that returns some new type variable. Letâs call it `x`

:

`... -> x`

and then we need to give eliminators for each case:

`-> elim_cons -> x elim_nil `

and then replace each eliminator with a function that takes its arguments, and returns `x`

. For `Nil`

, there are no arguments, so itâs just:

`-> elim_cons -> x x `

and then we do the same thing for `Cons :: a -> List a -> List a`

:

`-> (a -> List a -> x) -> x x `

of course, there is no `List a`

type anymore, so we replace that with `x`

too:

`-> (a -> x -> x) -> x x `

And thus we have our codata-encoded list. For bonus points, we can do a little shuffling and renaming:

`-> b -> b) -> b -> b (a `

which looks very similar to our old friend `foldr`

:

`foldr :: (a -> b -> b) -> b -> [a] -> b`

In fact, a little more reshuffling shows us that `foldr`

is exactly the codata transformation weâve been looking for:

`foldr :: [a] -> ((a -> b -> b) -> b -> b)`

Cool. The paper calls this transformation the âvisitor patternâ which I guess makes sense; in order to call this thing we need to give instructions for what to do in every possible case.

This is an encoding of the type itself! But we also need codata encodings for the data constructors. The trick is to just ignore the âhandlersâ in the type that donât correspond to your constructor. For example:

```
Nil :: (a -> b -> b) -> b -> b
Nil _ nil = nil
Cons
:: a
-> ((a -> b -> b) -> b -> b)
-> (a -> b -> b)
-> b
-> b
Cons head tail cons nil = cons nil (tail cons nil)
```

Really, these write themselves once you have an eye for them. One way to think about it is that the handlers are âcontinuationsâ for how you want to continue. This is the dreaded CPS transformation!

Letâs go the other way too. Appropriately, we can use codata streams:

```
Stream a where
codata head :: Stream a -> a
{ tail :: Stream a -> Stream a
, }
```

Iâm winging it here, but itâs more fun to figure out how to transform this than to get the information from the paper.

The obvious approach here is to just turn this thing directly into a record by dropping the `Stream a ->`

part of each field:

```
data Stream a = Stream
head :: a
{ tail :: Stream a
, }
```

While this works in Haskell, it doesnât play nicely with strict languages. So, we can just lazify it by sticking each one behind a function:

```
data Stream a = Stream
head :: () -> a
{ tail :: () -> Stream a
, }
```

Looks good to me. But is this what the paper does? It mentions that we can `tabulate`

a function, e.g., represent `Bool -> String`

as `(String, String)`

. It doesnât say much more than this, but we can do our own research. Peep the `Representable`

class from adjunctions:

```
class Distributive f => Representable f where
type Rep f :: *
tabulate :: (Rep f -> a) -> f a
index :: f a -> Rep f -> a
```

This thing is exactly the transformation weâre looking for; we can ârepresentâ some structure `f a`

as a function `Rep f -> a`

, and tabulating gets us back the thing we had in the first place.

So the trick here is then to determine `f`

for the `Rep f`

that corresponds to our `codata`

structure. Presumably that thing is exactly the record we worked out above.

Whatâs interesting about this approach is that itâs exactly scrap-your-typeclasses. And itâs exactly how typeclasses are implemented in Haskell. And last I looked, itâs the approach that Elm recommends doing instead of having typeclasses. Which makes sense why itâs annoying in Elm, because the language designers are forcing us to hand-compile our code! But I donât need to beat that dead horse any further.

Something that piqued my interest is a quote from the paper:

Functional langauges are typically rich in data types âŚ but a paucity of codata types (usually just function types.)

This is interesting, because functions are the only non-trivial source of contravariance in Haskell. Contravariance is the co- version of (the poorly named, IMO) covariance. Which is a strong suggestion that functions are a source of contravariance *because they are codata,* rather than contravariance being a special property of functions themselves.

I asked my super smart friend Reed Mullanix (who also has a great podcast episode), and he said something I didnât understand about presheafs and functors. Maybe presheafs would make a good next paper.

This was a helpful paper to to wrap my head around all this codata stuff that smart people in my circles keep talking about. None of it is *new,* but as a concept it helps solidify a lot of disparate facts I had rattling around in my brain. Doing this final tagless encoding of data types gives us a fast CPS thing that is quick as hell to run because it gets tail-optimized and doesnât need to build any intermediary data structures, and gets driven by its consumer. The trade-off is that CPS stuff is a damn mind-melter.

At Zurihac 2018, I met some guy (whose name I canât remember, sorry!) who was working on a new language that supported this automatic transformation between data and codata. I donât remember anything about it, except he would just casually convert between data and codata whenever was convenient, and the compiler would do the heavy lifting of making everything work out. It was cool. I wish I knew what I was talking about.

]]>I started by really trying to wrap my head around how exactly the `ana . cata`

pattern works. So I wrote out a truly massive number of trace statements, and stared at them until they made some amount of sense. Hereâs whatâs going on:

`ana`

takes an `a`

and unfolds it into an `F a`

, recursively repeating until it terminates by producing a non-inductive `F`

-term. So here `F`

is a `Sorted`

. And then we need to give a folding function for `cata`

. This fold happens in `Unsorted`

, and thus has type `Unsorted (Sorted (Mu Unsorted)) -> Sorted (Mu Unsorted)`

. The idea here is that the `cata`

uses its resulting `Sorted`

to pull forward the smallest element itâs seen so far. Once the `cata`

is finished, the `ana`

gets a term `Sorted (Mu Unsorted)`

, where the `Sorted`

term is the head of the synthesized list, and the `Mu Unsorted`

is the next âseedâ to recurse on. This `Mu Unsorted`

is one element smaller than it was last time around, so the recursion eventually terminates.

OK, so thatâs all well and good. But what does `ana . para`

do here? Same idea, except that the fold also gets a `Mu Unsorted`

term, corresponding to the unsorted tail of the list â aka, before itâs been folded by `para`

.

The paper doesnât have much to say about `para`

:

in a paramorphism, the algebra also gets the remainder of the list. This extra parameter can be seen as a form of an as-pattern and is typically used to match on more than one element at a time or to detect that we have reached the final element.

Thatâs all well and good, but itâs unclear how this can help us. The difference between `naiveIns`

and `ins`

is:

```
naiveIns :: Ord a
=> Unsorted a (Sorted a x)
-> Sorted a (Unsorted a x)
UNil = SNil
naiveIns :> SNil) = a :! UNil
naiveIns (a :> b :! x)
naiveIns (a | a <= b = a :! b :> x
| otherwise = b :! a :> x
ins :: Ord a
=> Unsorted a (c, Sorted a x)
-> Sorted a (Either c (Unsorted a x))
UNil = SNil
ins :> (x, SNil)) = a :! Left x
ins (a :> (x, b :! x'))
ins (a | a <= b = a :! Left x
| otherwise = b :! Right (a :> x')
```

Ignore the `Left/Right`

stuff. The only difference here is whether we use `x`

or `x'`

in the last clause, where `x`

is the original, unsorted tail, and `x'`

is the somewhat-sorted tail. Itâs unclear to me how this can possibly help improve performance; we still need to have traversed the entire tail in order to find the smallest element. Maybe thereâs something about laziness here, in that we shouldnât need to rebuild the tail, but weâre going to be sharing the tail-of-tail regardless, so I donât think this buys us anything.

And this squares with my confusion last week; this âcachingâ just doesnât seem to do anything. In fact, the paper doesnât even say itâs caching. All it has to say about our original `naiveIns`

:

Why have we labelled our insertion sort as naĂŻve? This is because we are not making use of the fact that the incoming list is orderedâ compare the types of

`bub`

and`naiveIns`

. We will see how to capitalise on the type of`naiveIns`

in Section 5.

and then in section 5:

The sole difference between sel and bub (Section 3) is in the case where a 6 b:

`sel`

uses the remainder of the list, supplied by the paramorphism, rather than the result computed so far. This is why`para sel`

is the true selection function, and fold bub is the naĂŻve variant, if you will.

OK, fair, that checks out with what came out of my analysis. The `ana . para`

version does use the tail of the original list, while `ana . cata`

uses the version that might have already done some shuffling. But this is work we needed to do anyway, and moves us closer to a sorted list, so it seems insane to throw it away!

The best argument I can come up with here is that the `ana . para`

version is dual to `cata . apo`

, which signals whether the recursion should stop early. That one sounds genuinely useful to me, so maybe the paper does the `ana . para`

thing just out of elegance.

Unfortunately, `cata . apo`

doesnât seem to be a performance boost in practice. In fact, both `cata . ana`

and `ana . cata`

perform significantly better than `cata . apo`

and `ana . para`

. Even more dammingly, the latter two perform better when they ignore the unique abilities that `apo`

and `para`

provide.

Some graphs are worth a million words:

These are performance benchmarks for `-00`

, using `Data.List.sort`

as a control (âsortâ). The big numbers on the left are the size of the input. âbubbleâ is the naive version of âselection.â Additionally, the graphs show the given implementations of `quickSort`

and `treeSort`

, as well as the two variations I was wondering about in the last post (here called `quickTree`

and `treeQuick`

.)

The results are pretty damming. In *all* cases, bubble-sort is the fastest of the algorithms presented in the paper. Thatâs, uh, not a good sign.

Furthermore, the âno cachingâ versions of âinsertionâ and âselectionâ both perform better than their caching variants. They are implemented by just ignoring the arguments that we get from `apo`

and `para`

, and simulating being `ana`

and `cata`

respectively. That means: whatever it is that `apo`

and `para`

are doing is *strictly worse* than not doing it.

Not a good sign.

But maybe this is all just a result of being run on `-O0`

. Letâs try turning on optimizations and seeing what happens:

About the same. Uh oh.

I donât know what to blame this on. Maybe the constant factors are bad, or itâs a runtime thing, or I fucked up something in the implementation, or maybe the paper just doesnât do what it claims. Itâs unclear. But hereâs my code, in case you want to take a look and tell me if I screwed something up. The criterion reports are available for `-O0`

and `-O2`

(slightly different than in the above photos, since I had to rerun them.)