Showing posts with label static. Show all posts
Showing posts with label static. Show all posts

Tuesday, October 20, 2009

Thank _____ for Static Analysis

Once I talked about why I think static typing (and more generally static analysis) can be really useful, even if I didn't say much. Today I was working on some code and was reminded yet again that, in some cases, without static typing all would be lost, and I would fall into a dark insanity, never to be lucid again.

The code is that of the Tac system, a "framework for implementing and interacting with interactive theorem provers". Originally it was conceived of as a generic tool with which you could develop first an interactive prover for a linear logic, then one for a classical, then another for an intuitionistic logic, all as easy as something relatively... un-difficult. As it stands we have fully developed one such prover, based on the logic µLJ, a first-order logic with (mutually recursive) definitions, induction and coinduction, and generic quantification.

Like Coq, Tac is a tactics and tacticals based prover, in which you manipulate logical sequents by applying tactics (possibly tactics composed of tacticals and tactics) to them. These tactics can be very low-level (a tactic for right conjunction elimination, say), or more complex (a rule for applying some hypotheses to a lemma). In addition, there's a tactic implementing automatic proof search based on focusing which can finds proofs that require nested inductions and coinductions by automatically generating invariants.

To give you an idea of how working with the system might work, here's a proof script showing the process of first defining the natural numbers, defining the even numbers, and then proving that 0 is even (yes, a silly proof) by first unfolding the definition of even, then picking the left branch, and finally recognizing that 0 does in fact equal 0.

#define "nat x := x = 0; sigma y\ x = s y, nat y".
#define "even x := x = 0; sigma y\ x = s s y, even y".

#theorem even_0 "even 0".
mu_r.
left.
eq.

We've also spent a lot of time working on implementing automatic proof search via focusing. Here's a slightly more interesting proof using automation, showing that if x is a natural number then either it is even or its successor is even:

#theorem even_nat "pi x\ nat x => (even x; even (s x))".
prove.

Not an interesting script though.

Anyway, one standard way of implementing such a system is through higher order functions and success and failure continuations (that paper, by Elliott and Pfenning, describes it in the context of logic programming search, but as it is powerful way of implementing backtracking search, it's what we want). The idea is that a tactic is just a function that takes a sequent, a success continuation, and a failure continuation. If application of the tactic on the sequent succeeds, the success continuation is called with the results of the application (more sequents). Otherwise, the failure continuation is called. Tacticals are functions taking possibly several tactics (a list of them, even) and returning a single tactic. Sometimes we have tacticals taking tacticals returning tacticals, and on and on. Lots of useful composable tacticals for doing all kinds of stuff -- the automatic proof search tactical is (more or less) just composed out of simpler tactics.

The point is, after all is said and done, we end up with some fairly complex code. Some of the types defined in the source are third and fouth order, to say nothing of those of the various helper functions whose types are inferred. Working with such complicated code can be pretty hairy, but with O'Caml's type system we've found it relatively easy to work with.

Now, you might say "you're doing it wrong" (I mean come on, fourth order functions?) But let me motivate the design of the system as it stands. First, the tactics and tacticals method of interactive theorem proving has demonstrated its merit in several other systems, so we'll not find a serious case of "doing it wrong" here. Second, the success and failure continuation based approach is, as mentioned, a powerful and elegant way of implementing backtracking search; it's efficient, because all calls are tailcalls, once you wrap your head around the technique it's easy to write new tacticals to compose various tactics correctly, and it cuts the crust off your toast.

Anyway, the point is that while I don't bother about types when writing Python scripts for a MUD I wrote, and I don't even use them to great effect in some of my other projects, sometimes they really are a ____-send.

Postscript 1: fill in the blanks as you see fit.

Postscript 2: Here's an example of the kind of proof that Tac can find; this one is of the fact that "less than or equal to" is transitive. The proof is presented in outlined form, where asynchronous phases are compressed. Click it for a better view.

Tuesday, August 18, 2009

Static is only missing an N

So, I said that some folks might not be interested in static analyses like type correctness, or const correctness, or god knows what. And, to be honest, I can't blame you (too much), because in a lot of popular langauges the available static analyses (the type system, in most cases) isn't that great. Maybe it isn't powerful enough to encode useful properties (C, anybody?), or maybe it is powerful enough if you really want it to be, but everyone sort of ignores it because its more trouble than it's worth. Anyway, lot's of people aren't all about static typing, are all about dynamic typing, and will kill you with an ASCII blade if you disagree. On the other hand, some people love static typing, and static analysis in general, more than they love their own aged grandmothers. They will fight you (and any passing dragons) to the death with the dual weapons of dataflow analysis and syntax directed translation. Or something.

Anyway, the point is that I'm about to jump into the fray, so to speak, and try to convince you that thinking about static typing for a language like Mix is really a good idea. So, away we go!

First, there are a number of reasonable complaints about the static type systems of popular existing languages (clearly this list isn't even close to exhaustive, but it hits a few):

  1. They require a ton of extra code (everyone say "Java" with me).
  2. They can be subverted, making them not particularly useful (C's type system accepts code that will set you on fire; CCured is an interesting attempt to provide you with a fire extinguisher by translating flamable C to safe(r) C).
  3. They limit your freedom as a programmer, making programming slower.
  4. Their usefulness is subsumed by runtime tests.

Ok, lot's of arguments against static typing. The first one I'll give away: lot's of languages have got it "wrong" according those voicing this particular complaint. But, others have gotten it right (I'm thinking O'Caml, Haskell, F#): type inference can save a language from failing this early in the game.

The second one we can give away in the same fashion: Java, C, C++, and all kinds of others share this problem, to varying degrees: in Java you can get a ClassCastException, and in C you can cause a core dump. But again, there are plenty of languages whose type system you cannot subvert to cause runtime problems, so stronger static typing could save us from this failing as well.

The third one is were the complaints start "getting real", to quote someone or another. Basically, the idea is this: when you're programming, especially during the early phases of a project, or when you're really just "exploring the solution space" (buzzword!), or doing rapid development, types can make your code less flexible. It can take longer to twist things, experiment. There's all kinds of inertia to the code, damnit. Well, again this can be true. Here's my argument: if there isn't any manifest typing (in the form of type annotations), and you have a "good" type system, then changing your program around in this manner should only ever irritate the type checker when you've actually done something wrong, which is exactly what you want, right? Of course, the problem becomes defining "good"; that's what this blog is about.

The fourth one is tricky as well. I read about this one in a book edited by Joel Spolsky called The Best Software Writing, in a piece by Bruce Eckel; go read this if you haven't (only takes a minute). Back already?

Ok, so the argument is that you're already writing tests (right?), and they already check all of the things that the type system checks, and more, so why bother with static typing? Good point, certainly. But, this seems to rely on 2 basic facts, at least 1 of which must be true.

  • Using a language with static typing costs something.
  • Dynamic typing saves that same something (time, or money, or whatever).

Hopefully the above (points 1 through 3) has convinced that, while the first bit can be true, it doesn't have to be. As for the second bit, it's predicated on the assumption that you can't have a statically typed language that saves you the same something in most cases; we'll see about that!

So, if you take all my claims as true, you might be wondering why I went through all that trouble only to basically show that static and dynamic typing are more or less equivalent. Because that's the result of point 4 above, right? Well, here's where I'm going out on a limb to give static typing the edge (under certain conditions, but don't worry, dynamic typing gets to win, too; we're all winners here). In my personal experience, most of the features of dynamic languages that I use regularly could (probably) be incorporated in a sbuitably designed static type system (of course, things like eval aren't in the "most" I'm talking about; if you need stuff like that then dynamic takes the game).

Not much content here, but maybe if you were on the fence before you'll hear me out going forward.