Using function composition to compose translations and other compiler passes makes it really clear what passes are involved. Such compositions are a great way to write code, especially in the UFT driver, but it’s not always easy to get them to type check. This short note presents some heuristics that make it easier.

The heuristics include a trick: I redefine `!`

to be a synonym for `Error.map`

. I can then write `>>> !`

, which looks sort of like an infix operator, even though it isn’t really. With that trick, I can compose two functions \(f\) and \(g\) based on what type comes out of each function. (I assume that no `error`

type ever goes directly into either function.)

If neither function returns an

`error`

type, compose them with`>>>`

.If both \(f\) and \(g\) return

`error`

types, compose them with`>=>`

.If \(f\) returns an

`error`

type but \(g\) does not, compose them with`>>> !`

.If \(f\) returns ’b error list, for any

`'b`

, use`>>>`

to compose \(f\) with`Error.list`

. In this situation, no other \(g\) is useful. But the composition \(f\) >>> Error.list can then be composed with a function \(g'\) that expects an input of type`'b list`

.

Here’s a table of types:

\(f\)’s type | operator | \(g\)’s type (or \(g\)) | composition’s type |
---|---|---|---|

`'a -> 'b` |
`>>>` |
`'b -> 'c` |
`'a -> 'c` |

`'a -> 'b error` |
`>>> !` |
`'b -> 'c` |
`'a -> 'c error` |

`'a -> 'b error` |
`>=>` |
`'b -> 'c error` |
`'a -> 'c error` |

`'a -> 'b error list` |
`>>>` |
`Error.list` |
`'a -> 'b list error` |

And here’s an algebraic law:

`f >=> (g >>> h) === f >>> ! g >=> h`

I recommend using this law from left to right, so it can be used to un-nest pipelines.