Debugging, for me, is often, the compiler isn’t generating good code so I’meyeballing the state of its entrails. Or, take this little source program;compile it this far; look at that. That’s debugging for me. It’s seldom singlesteppingthrough the program—it’s more looking at values of different partsin compilation.
I don’t even have any very sophisticated Emacs jiggery-pokery. Which somepeople do. There’s also a whole world of people out there who are used toVisual Studio and Eclipse kind of IDEs. I think a cultural barrier to adoptionof functional programming languages is partly that we haven’t got the IDEstory sorted out. There’s a bit of a chicken-and-egg problem here. At themoment the chicken is getting busier—there’s more interest in functionalprogramming generally. I’m hoping that will provoke work on the egg. It’s alot of engineering to build an IDE for Haskell. Even with Visual Studio as ashell or Eclipse as a shell, there’s quite a lot of work in making a plugin that’sreally smooth and does everything right.
Seibel: GHC has a read-eval-print loop, GHCI. Do you tend programHaskell interactively?
Peyton Jones: Actually, I tend to mostly edit and compile. But otherpeople just live their whole lives in GHCI.
Seibel: When it comes to testing, I suppose one of the nice things aboutfunctional languages is when you want to test some little function in thebowels of your program, you just have to figure out what form is its inputgoing to be.
Peyton Jones: Well, for me, if the input data is simple enough that youcould do that, it’s probably not going to be the problem with my program.The problem with my program is going to be some fairly humongous inputprogram that GHC is trying to compile and getting the wrong answer for it.
Testing is, I think, frightfully important for writing down properties andQuickCheck properties are really useful—QuickCheck is a Haskell libraryfor generating random tests for a function based on its type. But I was tryingto think why I don’t use QuickCheck—which is a very nice tool—more. Ithink it’s because the situations that cause me trouble are ones that I wouldfind it difficult to generate test data for. In any case, there are loads ofpeople out there generating programs that make GHC barf in one way oranother. That’s what GHC’s bug tracker is about.
So typically I’m starting with something that’s not right, already. Maybe thecompiler could just fall over altogether or reject a program when itshouldn’t. Or it could just generate suboptimal code. If it’s just generatingbad code, I’ll look at the code at various stages in the compilation pipelineand say, “It looks good then; it looks good then. Bah, it’s gone bad here;what’s gone wrong?”
Seibel: So how do you actually look at it?
Peyton Jones: GHC has flags that let you say, in rather a batch-dumpy kindof way, “Just print out various things.”
Seibel: Built-in print statement debugging?
Peyton Jones: Yes. And it’s aided by the fact that the structure is like mostcompilers: it has this top-level structure of a pipeline of things that happen.If something’s gone wrong in the middle of one of these passes, then thatcould be a bit trickier. But I tend to use rather unsophisticated debuggingtechniques. Just show me the program before and after this pass. Aaah, I seewhat’s going wrong. Or sometimes I don’t see what’s going wrong so then Imight scatter a few unsafe printfs around to show me what’s actually goingon.
There are various debugging environments for Haskell—a summer student,Pepe Iborra, did a nice one earlier this year which now comes with GHC,which is an interactive debugger of some kind. Which I’ve not used verymuch yet. Partly because we haven’t had one for so long, because it’s lessobvious how do you single-step a functional program.
It’s been a kind of interesting research question of how you go aboutdebugging functional programs for some time. It’s a bit embarrassing that wecan’t tick that box in a straightforward way, but that makes it an interestingresearch problem.
That was the long way around of saying that I tend to use terribly crudedebugging techniques with unsafe printfs. And I’m not very proud of that.But for a long time we didn’t have anything else. At least as far as GHC isconcerned, I’ve evolved mechanisms that mean that’s the shortest path tocompletion for me.
Seibel: That seems to be a common story. It sort of makes you wonderabout the utility of writing better debuggers if so many people get by withprint statement debugging.
Peyton Jones: There’s a cultural thing though. On the .NET platform withdebuggers that people have put tens or hundreds of man-years intoengineering, I think it’s a qualitatively different experience. I think debuggersdo require perhaps more engineering cycles to get to work well. But if youput them in, you do get something that is really quite remarkably morehelpful.
Maybe the people that you’ve been mainly talking to are more in theacademic software mold. And also perhaps have grown up withsophisticated debugging environments less. I wouldn’t like to draw anygeneral lessons. I certainly wouldn’t wish to denigrate or downplay theimportance of good debugging environments. Particularly in these rathercomplicated ecosystems where there are many, many, many layers ofsoftware. GHC is a very simple system compared to a full-on .NETenvironment with layers of DOMs and UMLs and I don’t know what kind ofgoop. The real world gets so goopy that more mechanical support may wellbe really important.
Seibel: Another approach to getting correct software is to use formalproofs. What do you think about the prospect of that being useful?
Peyton Jones: Suppose you declare that your goal is for everything to havea machine-checked proof of correctness. It’s not even obvious what thatwould mean. Mechanically proved against what? Against some specification.Well how do you write the specification? This is now meant to be aspecification of everything the program does. Otherwise you wouldn’t haveproved that it does everything that it should do. So you must have a formalspecification for everything it should do. Well now—how are you going towrite that specification? You’ll probably write it in a functional language. Inwhich case, maybe that’s your program.
I’m being a bit fast and loose here because you can say some things inspecification languages that you can’t say in programs like, “The result of thefunction is that y such that y squared equals x.” That’s a good specificationfor a square-root function but it’s not very executable. Nevertheless, I thinkto try to specify all that a program should do, you get specifications that arethemselves so complicated that you’re no longer confident that they saywhat you intended.
I think much more productive for real life is to write down some propertiesthat you’d like the program to have. You’d like to say, “This valve shouldnever be shut at the same time as that valve. This tree should always bebalanced. This function should always return a result that’s bigger thanzero.” These are all little partial specifications. They’re not completespecifications. They’re just things that you would like to be true.
How do you write those down? Well, functional languages are rather goodat that. In fact this is exactly what happens when you write a QuickCheckspecification; you write down properties as Haskell functions. Say we wantto check that reverse is its own inverse—well, you might writecheckreverse with type list of A to bool. So checkreverse of xs is reverseof reverse xs equals xs. So this is a function that should always return true.That’s what a property function is. But it’s just written in the samelanguage—so that’s nice.