Tuesday, October 13, 2009

Moving out

At www.loup-vaillant.fr.

It will be polished. I promise. Eventually.

Edit: it is polished. At last.

Monday, June 29, 2009

Assignment Considered Harmful

In his essay "Go To Statement Consedered Harmful" Edsger W. Dijkstra demonstrated how the use of goto made programming harder. Now, goto is considered harmful, and has been replaced by more reasonable constructs. This essay is an attempt to demonstrate that assignment is harmful in the same way. But first, just to be clear:

  • A variable is a place in which a value is stored. Not to be confused with the value itself.

  • An assignment is the replacement of the value currently stored in a variable by another. Not to be confused with initialisation, wich is the the placement of a value in a new variable.

Simply put, assigment isn't mandatory, confuses the word "variable", and impedes program analysis, variable naming, refactoring, and performance.

Assignment is not mandatory

Assignment has two purposes. The first is storing a value for later use. Initializing a new variable can do that, and is less disruptive. The second is the construction of loops. Recursive function calls can do that, but many programmers find plain loops more readable. Fortunately, even loops are rarely mandatory. The factorial is a case in point (here in pseudo-code):

fac n = product [1..n]

This is just a definition. The natural way to implement it in an imperative language is to use a loop (here in C):

int fac(int n)
  int acc = 1;
  while (n > 0)
    acc *= n--;
  return acc;

It hardly looks like the definition of a factorial. The product and the sequence of integers are there, but interleaved, somewhat hidden. There is a better way (here in Haskell):

fac n = product [1..n]

This is actual, runnable code. [1..n] denotes a list of integers, ranging from 1 to n. product is a function (not a primitive), which takes a list as argument and returns the product of its elements.

This was just an example. In real code, there is all sorts of loops. However, they follow a few well known patterns, just like goto did. These patterns have been captured in recent programming languages like Haskell, just like the patterns of goto had been captured in imperative languages.

Now, in a reasonable programming language, loops are hardly needed, and assignment is not needed at all.

Assignment makes the term "variable" confusing

Compare these two programs:

(* Ocaml *)           |     /* C */
let x = ref 1  in     |     int x = 1;
let y = ref 42 in     |     int y = 42;
  x := !y;            |     x = y;
  print_int !x;       |     printf("%d", x);

In the Ocaml program, the "ref" keyword and the "!" operator make a clear distinction between a variable and its value. In C, such disambiguation is made from context.

All popular imperative programming languages are like C in this respect. This leads to many language abuses, like "x is equal to 1, until it is changed to be equal to y". Taking this sentence literally is making three mistakes:

  1. x is a variable. It can't be equal to a value (1). A glass is not the drink it contains.

  2. x and y are not equal, and will never be. They represent two distinct places. They can hold the same value, though. That two different glasses contain the same drink doesn't make them one and the same.

  3. x doesn't change. Ever. The value it holds is merely replaced by another. A glass doesn't change when you replace its water by wine.

The gap between language abuse and actual misconception is small. If we have any misconception about variables, even temporary, how can we hope to write correct programs?

Assignment makes program analysis harder

Compiler writers have understood that for quite some time. Now, a typical compiler for an imperative language will first transform the source code to SSA, an intermediate form where assignment is basically banned. This makes optimization simpler.

This also apply to manual analysis. Imagine the everyday situation of trying to understand the code of a colleague;

int x = 42;
 * Big Blob of Code
printf("%d", x); /* What does it print? */

Maybe it prints 42. Maybe not, because x may have changed (whoops, sorry, may not contain 42 any more). To be sure, we have to look at that big blob of code. Forgetting that may introduce a bug. Without assignment, the dependency chain is obvious, and can't be ignored.

Assignment makes variable naming harder

When you know a variable will allways have the same value, you name it after that value. If this value can change, you have to consider all possible values. A name rarely scale that well. That makes code harder to understand.

For instance, in my C implementation of the factorial, I named the accumulator of the loop "acc". As a name for an accumulator, this is accurate. However, the last value of acc was the factorial of n. A good name to reflect that would have been "fac_n". Neither name is satisfactory because each misses something important.

Assignment makes refactoring harder

There is a very important, often overlooked, rule about programming: the more you allow, the more you prevent. For each thing you allow in a program, you have to drop a set of assumptions about it. As a result, some manipulations become unsafe or impossible.

In high school, a definition like "let a = x + 1" meant any occurence of "a" or "(x + 1)", can be replaced by the other without changing the meaning of what is written. They are equivalent, and therefore substitutable. Imperative programs are more complicated:

int x = 42;
x = 1;
printf("%d", x); // try and replace x by 42!

Without assignment, "x" and "42" would be equivalent and substitutable. Because they are not, refactoring is harder.

Assignment hurts performance

Optimization during compilation can be seen as a form of refactoring. Harder refactoring means harder optimizations. It complicates the compiler and make it generate less efficient code. SSA form mitigate this problem, but don't eliminate it.

Another thing you lose when you allow assignment is sharing. It becomes important when you manipulate relatively complicated data structures, such as associative maps. There are three obvious ways to manipulate a data structure:

  1. Directly modifying it (assignment allows that).
  2. Create a new structure by copying the whole thing.
  3. Create a new structure by referencing the old one. (The unchanged parts are shared).

Each way have a specific problem. Way 1 is effectively an assignment, with all the disadvantages mentioned above. Way 2 wastes time and memory. Way 3 is unsafe if you ever use way 1. If assignment is allowed, way 2 is often your only safe choice. If it is not, way 3 is safe and convenient and efficient.

These problems are pervasive

Using assignment sparringly is one thing. Compromises must be made, for instance to achive the best possible performance on a critical section of the code, or in high performance applications like device drivers. Using assignment everywhere is another thing. Often a big ball of mud.

A piece of advice

Now I made my point about how assignment is harmful, I would like you to take action so it is less used. So please:

  • Learn a purely functionnal language —I suggest Haskell. It will show you how you can do without assignment, and what are the advantages of not having it. Beware, though: it may be addictive.

  • Push for better languages. At the very least, demand garbage collection. Manual memory management is a big consumer of assignments.

  • Avoid making functions which modify their arguments, or object methods which modify the object. They're not easily composable and often lead to verbose programs. In short, don't make assignment happy APIs —often difficult without automatic memory management.

Thursday, March 12, 2009

The danger of "language" in programming

I recently stumbled upon this blog entry from Steve Riley. There are a number of things in this that can be agreed with or argued about, but one in particular striked me as especially dangerous:

"[C++] has a million nuances (like the English language) that somehow make expressing exactly what you want to say seem easier than other languages."

I can interpret this sentence in three ways. The first is meaningless. Just some pretty wording to impress people, only to say "C++ is good". I doubt Steve was that sloppy. The second is litteral. C++ share a trait with natural languages: the "million nuances". I don't think that was the intended meaning, though it was how I first understood it. The last (and most likely) is the analogy. C++ and English have more nuances than other languages —programming and spoken, respectively. I call it biased towards his native tongue, but his point remains.

Anyway, comparing natural and programming languages disturbs me, because drawing the line between similarities and differences is hard. That makes the jump from right premises to wrong conclusions way too easy. Analogies are a great tool in many cases, but when talking about computers, they just don't work

There is a fundamental difference between the two kind of languages wich is often overlooked: their usage. Natural languages are used to talk to people, while programming languages are used to talk to machines —and people too, if not primarily. The difference between machines and people is this: a machine does as it's told, a person does what's needed.

In virtually each use of a computer, we have some specific need, about which we tell the computer, using some programming language(s). (This need may evolve, but that's not the point.) The resulting description of this need is the program. What often happens at this point is a disagreement between the computer and the human about the meaning of this program —wich is a bug.

There are two ways to deal with bugs. Correcting them, and avoiding them. The production of a program of any kind always combine both aproaches. Most of the time, the (explicit) focus is on correction. Sometimes, it is on avoidance.

Of these two, the only one that is significantly influenced by the structure of programming languages is avoidance. It is therefore crucial to insist on it when choosing (or designing) one. Several characteristics of programming languages can help or impede bug avoidance. Conciseness can help, for it is known that the defect per line of code is pretty constant accross languages. A paranoid type system can help, if it is still convenient. But I think what helps most is obviousness. The more obvious, the better. Ideally no special case, no subtlety, no nuance. By itself, the language should be boring.

That leads us to why Steve's above sentence is so dangerous. Not because it's wrong (although it is), but because it's right. C++ does have many nuances. It is a very interesting and very subtle language, to the point even machines (namely compilers) disagree about its meaning. That is not good. That is a recipe for disaster.

Sunday, January 25, 2009

Version control for small projects

Basically, as a developper, you care about one thing and one thing alone: your code. You want to modify it, archive it, share it, and many other wonderful things.

Without special tools, archiving is often achieved by copying the source code to some safe place from time to time. Can be dealt with but definitly not ideal. Sharing is achieved the same way, the safe place being whatever email adress your fellow developpers happen to use. Way messier:

  • You: (happy): Hello, Bud? I just made some bugfixes on the GUI. I have changed display.c, button.c, and main.c.
  • Bud (cautious): Just these files?
  • You: Oh, and the corresponding .h, of course
  • Bud (doubtful): Are you sure? what about window.c?
  • You (panicked): ...
  • Bud (resigned): OK, just send me the whole code, I'll deal with it.

Sounds familliar? You just had to keep track the exact changes you made since last time you gave your code to Bud, yet you didn't. Neither could I.

Sorting it out

Fortunately, version control systems do exactly this. So let's start with centralized version control systems, like SVN. These are the easiest to grasp. When using SVN, your code is stored in two places: the central repository, and your working copy. These have two major differences: number and persistence.

The central repository is one, the reference to which which every modifications will go. The working copies are many (typically one per developper),sandboxes in wich you create and test your modifications.

Nothing is ever lost in the central repository. The entire history of the code base is stored there. Need a safe place to store erlier version of your code? This is it. Time Machine 20 years before Apple. The working copy, on the other hand, is changed directly. If you want those change to be recorded, you must push them to the repository.

The workflow in a centralized version control system is quite simple:

  1. Pull the modifications from the repository to your working copy.
  2. Make some changes to your working copy (and test them).
  3. Push the changes from your working copy to the repository.
  4. Repeat the cycle.

If you and your teammates try to change the same piece of code, the version control system will tell, and tell where. No more need for perfect memory nor guesswork. You get straight to the point and solve that conflict.

Now the problem is setting it up. You need a central server, which ideally should be online 24/7. Not an option for many small projects. Even when this service is provided for free, it can be too much of a hassle.

DCVS to the rescue

Distributed version control systems like Darcs differ from centralized ones in one respect: repositories are now many. Yes, everyone has a fully fledged repository, with all history and such. This gives distributed CVSes two big advantages over centralized ones. First, they are esier to set up. No need for a central server, and a few commands are enough to make a repository out of an existing project. Second, you can keep the way you worked before you had them. It is just easier. Basically, the above phone conversation would become this:

  • You (happy): Hello, Bud? I just made some bugfixes on the GUI.
  • Bud (cautious): Just the GUI?
  • You (happy): Yup.
  • Bud (happy): OK, send me the patch, then.

Just easy. You just have to type one command to get the said patch, so you can email it to Bud. No need to keep track of each tiny little change you made. Your DCVS knows. If you and Bud changed the same thing, your DCVS will tell, and tell where. Use them.