I first was keyed into this article of David Turner (see also this nice blog post about it) by a comment somewhere online (can’t find it now) from Conor McBride. Between functional programming as we know and love it in Haskell, OCaml, even untyped variants like Racket, on the one hand; and mighty type theory (TT) on the other, there is strong FP. What a great idea! In Strong FP, we are interested in termination of programs, just like in constructive type theory based on the Curry-Howard isomorphism. But unlike in type theory, termination is the only property we care about beyond those established by usual static typing. Strong FP does not make use of dependent types, for example. It is an intermediate point between FP and TT.

I was really happy to learn about the identification, by Turner, of this intermediate point. This is partly for reasons of personal history. I first programmed, as a boy, in BASIC, then some Pascal, and then the appalling C++ (genetic algorithms, in high school). In college I learned Scheme (those were the good old days) and Java, and then in grad school it was back to C++ to implement most of the first version of the CVC SMT solver. Awful coding experience to write so much C++ for symbolic manipulation. Then I discovered … was it OCaml? Haskell? No, a formalism coming from term rewriting called the rho-calculus. The parts I think I was drawn to were accomplishing what we usually use datatypes and pattern-matching for in functional programming. So then as an assistant professor it was OCaml, also Coq along the way. As an associate professor it was Agda, and finally now Haskell is my language of choice for everyday programming. Of course, I have been working on developing my own languages, with Cedille being by far the most interesting and successful attempt.

What I want to share from this little history is that I came to functional programming from more mainstream programming, and eventually became convinced of its superiority for expressing computational ideas elegantly and concisely. FP is great! And Strong FP is greater!

Why do I think Strong FP is even better than FP? Again, it is coming from personal experience. When I moved from programming in C++ to OCaml, it was such a fantastic experience. As I got used to OCaml for language implementation, pattern-matching, type inference, recursion, and such were just such a perfect fit and resulted in such nice code, I couldn’t believe how great it was. I had heard about this Haskell language, and the fact that you had to use some abstraction called monads to do IO. I thought this was absurd. IO is basic to programming, and the fact that some elaborate scheme is required for it in a language to me was almost a reductio ad absurdum for that language.

But then I actually tried writing some pure functional programs for things I cared about. At first it was rather hard and unintuitive, but when I finally got my code (I do not remember what this was for, but I have a distinct recollection of the experience) through the compiler (and it might have been Agda at that point instead of Haskell, I don’t remember), it just worked. It was tricky and maybe I even had some bad experience writing it in OCaml using mutable data structures — or maybe I just wrote it in pure style in OCaml. The details are lost to my memory. But the important point is not: despite the fact that it seemed like a straightjacket, pure FP led to a better solution to my problem.

And if you think pure FP is pure, wait till you get a load of strong FP, which is just pure FP with a requirement of uniform termination for your programs (they have to terminate for all inputs). If FP shows that more restrictions — deeply motivated, natural, elegant restrictions — leads to better code, strong FP could be expected to do this even more. Even if not, we get static assurance of termination, which could be a big improvement to code quality (and Turner provides other arguments in favor).

If this is such a great idea, why haven’t we all heard about strong FP and why isn’t there -XStrongFP for ghc? I do not know the social history or anything like that, but on the technical side, it looks like there is a simple explanation: strong FP has to bite off the part of TT that has caused some of the most difficulties, namely termination checking. The requirements Turner lists for recursion and inductive data are (1) primitive operations must be total (no problem if we have none!), (2) only positive recursive types are allowed (yes, we know this one well from TT and it creates some headaches but nothing too terrible), and (3) recursion must be structurally decreasing. It is (3) that is quite brittle and has led to interesting research on, for example, type-based termination. I glanced at a later paper of Turner’s on strong FP, and there was a fancier version of (3), but still it requires some syntactic check on program code separate from typing.

I believe that strong FP can make an exciting surge forward if it is type-based. But not in the sense of Barthe et al., and others (Abel comes to mind), where, at a high level, we enrich types with sizes of data structures, and use recursion on those sizes to ensure termination. This approach will not work for strong FP, if we conceive strong FP as by design something weaker than TT. Let us not allow ourselves dependent or term-indexed types (no refinement types, nothing like that). We essentially just have System F (or F-omega, or something similar), and we want to use typing to ensure termination. Is this possible?

Of course it is! System F’s types ensure termination already. But we don’t want to program with Church-encoded data in pure lambda calculus. We would like to use familiar notation for pattern-matching and recursion. Of course we understand the recursion has to be restricted somehow to ensure termination. And I am saying that we should set ourselves the goal of using typing — without dependent types — for such restriction. Can this be done?

No. Wait, yes, of course, that’s why I am leading into this this way. In Cedille, we have implemented a (type-based) form of histomorphic recursion, based ultimately on Mendler-style lambda encodings of data, which have efficient accessors; but this is not actually relevant for considering the interface. It is sufficient to understand that we have a proof of termination for any term whose type is a datatype (and this is enough, since recursive functions must then terminate since any call to them that produces a value in a datatype is terminating). And this type-based histomorphic recursion is a big extension in practical expressive power to syntactically checked structural recursion. When you are coding a pattern-matching recursion in Cedille for some function F, in the various clauses for F (that is, the cases for different forms of the inductive data you are doing a pattern-matching recursion on), you get the following:

a type Type/F, which abstracts (hides) the datatype;

a function F which takes in something of that abstract type Type/F, and then returns whatever you are defining F recursively to return; and

evidence that Type/F is D-like, where D is the datatype you are matching on, and being “D-like” allows you to: pattern-match on the data cast the data back to type D.



This interface has nothing to do with sizes of data structures. And yet it is sufficient to code some quite tricky examples. We can do natural-number division this was as iterated subtraction. Ok, that is not too tricky but in Coq and Agda the code uses a four-argument function that merges subtraction and division so the termination checker can verify it is terminating. Yuck. We can do a form of mergesort, which is definitely in the realm of trickier. In Agda this is done with sized types. The challenge problem we just managed to solve as a strong functional program using Cedille’s histomorphic recursion is Harper’s continuation-based regular expression matcher, which has been posed as a challenge problem by several researchers in the theorem-proving community. Coq has a solution, for example, that requires reasoning about the size of the list that is being consumed by the matcher, particularly as one calls the continuation. Our solution does not use dependent types at all. So in some sense, there is no termination reasoning. Instead, one has to code to the above interface, which certainly is tricky in this case, but again, requires no formalized reasoning about terms and functions. It just requires programmer ingenuity to figure out how to fit your algorithm into that interface.

Well, I’ve been going on enough about this. Hope all is well with any QA9 readership making it this far. 🙂