On programming language design

InfoQの以下の記事経由で、
Andrej Bauer氏の語るプログラミング言語の設計
こういう記事を見つけたので、
On programming language design | Mathematics and Computation
日本語に(勝手に)訳してみました。
英語が得意なわけでもないので(ていうか苦手なほうなので)変なところ等ありましたらご指摘願います。

――というかHaskellをちゃんと勉強したくなった。

In a recent post I claimed that Python’s lambda construct is broken. This attracted some angry responses by people who thought I was confused about how Python works. Luckily there were also many useful responses from which I learnt. This post is a response to comment 27, which asks me to say more about my calling certain design decisions in Python crazy.

最近のポストで、私は、Pythonのlambdaの機構は壊れていると主張した。このポストは、私がPythonがどう動くかについて混乱していると考えた人々による、いくつかの怒った応答を呼び寄せたが、幸運にも、そこから私が学ぶことができる、多くの有用な応答も含まれていた。このポストは、私が、Pythonの設計上の決定について狂っていると称している点について、もっと語ってほしいと私に依頼したコメント27番への応答である。

Language design is like architecture. The architect is bound by the rules of nature, he has to take into account the properties of the building materials, and he must never forget the purpose that the building will serve. Likewise, the designer of a programming language is bound by the theorems of computability theory, he must take into account the properties of the underlying hardware, and he must never forget that the language is used by programmers.

言語設計は建築に似ている。建築家は自然の規則に縛られ、建築材料の特性を考慮に入れなければならない。そして、彼はビルが今後役に立つであろうその目的を決して忘れてはいけない。同様に、プログラミング言語の設計者は計算可能性理論の定理によって縛られ、基盤となるハードウェアの特性を考慮に入れなければならない。そして、彼は言語がプログラマによって使用されるということを決して忘れてはいけない。

When I teach the theory of programming languages, I tell my students that there is a design principle from which almost everything else follows:

“Programmers are just humans: forgetful, lazy, and they make every mistake imaginable.”

私がプログラミング言語の理論について教えるとき、私は学生に、他の設計原理のほとんどすべてが従うべき設計原理がある、と教える。
 「プログラマはただの人間である――忘れやすく、怠惰で、およそ想像できるすべての誤りをしでかしてくれる――」

Therefore, it is the task of the designer to make a programming language which counters these deficiencies. A language must not be too complex, lest the programmer forget half of it. A language must support the programmer’s laziness by providing lots of useful libraries, and by making it possible to express ideas directly and succinctly. The language must allow good organization of source code, otherwise the programmer will use the copy-paste method. The language must try really hard to catch programming mistakes, especially the mundane ones that happen to everyone all the time. When it finds a mistake, it must point to the true reason for it, preferably with an error message that humans understand.

したがって、言語設計者がやるべきことは、これらの欠点に対抗するようなプログラミング言語を作ることである。プログラマがそれを半分忘れてしまったりしないよう、言語は過度に複雑であってはならない。言語はたくさんの便利なライブラリを提供し、かつアイディアを直接的かつ簡潔に表現できるようにすることで、プログラマの怠惰さをサポートしなければならない。言語は、ソースコードのよい組織化を可能にしなければならない。さもなければプログラマはコピー&ペーストを使ってしまうであろう。言語は、プログラミング上のミスを捕らえるよう最大限の努力をしなければならない。とりわけ誰にとってもいつでも起きるようなありふれたミスについては。ミスを見つけたときには、なるべく人間が理解できるエラーメッセージとともに、その理由を正確に指摘しなければならない。

You will notice that so far I have not said a word about efficiency. If this were the year 1972 we would talk about efficiency first and forget about the programmers, because 37 years ago hardware and processing time were the scarcest resources. Today we live in different times when the most expensive resource is development time. In 1972 it was a good design decision to implement arrays in C so that they did not carry with them information about their lengths (save a couple of bytes on each array), it was a good decision not to check for out-of-bounds errors in array indexing (save a couple of CPU cycles), and it was a good decision not to have garbage collection (it didn’t work well anyhow). From today’s point of view all these decisions were horrible mistakes. Buffer overflows, which are a consequence of missing out-of-bounds checks, cost the industry huge amounts of money every year, while lack of automated garbage collection results in memory leaks that cause programs to be unstable.

あなたも気づくように、私は今までのところ効率という言葉を使っていない。もしこれが1972年であれば、我々はまず効率について議論しプログラマのことを忘れ去ってしまったことであろう。なぜなら37年前は、ハードウエアとCPU時間が最も不十分な資源であったから。今日では、我々は、最も高価なリソースは開発に要する時間である、という異なった時代に生きている。1972年においては、(配列ごとに数バイトを節約するために)それ自体の長さに対する情報を持たないというCの配列の実装は、よいデザイン上の決定であった。(数CPUサイクルを節約するために)配列参照にて範囲エラーのチェックを行わないというのもよい決定であった。そしてガベージコレクション(それはいつもうまくいかない)を行わないというのもよい決定であった。今日の視点から見れば、これらの決定は恐ろしい間違いであった。範囲チェックの欠如から引き起こされるバッファオーバーフローは、毎年産業界に莫大な金額を支払わせており、おまけに自動ガベージコレクションの欠如は、プログラムを不安定にするメモリリークをもたらしている。

Of course, even today C might be just the right tool for your specific task. I am not saying that memory efficiency and speed are not important. They are not as important as they used to be. The first objective in a programming language design today should be friendliness to programmers. A lot is known about how to write an optimizing compiler and how to generate efficient code, so usually the design of the language does not prevent generation of efficient compiled or interpreted code.

もちろん、今日においてもCは特定のタスクにおいてはよいツールでありうる。私はメモリ効率や速度が重要でないとは言っていない。それらがかつて重要であったのと同じようには、現在では重要ではない、と言っているのである。今日では、プログラミング言語のデザインにおける最初の目標は、プログラマに対し親切であることである。最適化コンパイラの書き方や効率的なコード生成の方法については多くのことが知られており、言語のデザインが効率的なコンパイルやインタープリトを妨げることは通常ない。

People do not make bad design decisions because they are evil or stupid. They make them because they judge that the advantages of the decision outweigh the disadvantages. What they often do not see is that they could have achieved the same advantages in a different way, without introducing the disadvantages. Therefore, it is very important to get the order right: first make sure the design avoids the disadvantages, then think about how to get the advantages back.

人々は、邪悪であったりおろかであるから悪いデザイン上の決定をしてしまうわけではない。彼らは、その決定による利点が欠点を上回ると判断したからそうしたのだ。かれらがしばしば見落とすのは、他の方法を取ることで、欠点を取り込むことなく利点を獲得できたかもしれないということだ。よって、正しい順番がきわめて重要である。まずデザインは欠点を避けるべきであり、それから利点をいかに取り戻すかを考えるべきなのだ。

Let us now apply these principles to several examples.

いくつかの例に、この原則を適用させてほしい。

未定義値(NULL, null, undef, None)――Undefined values (NULL, null, undef, None)

Suppose we want a language with references (pointers in C). The principle tells us that it is a bad idea to allow invalid references because programmers will create them. Indeed, most recently designed languages, say Java and Python, do not allow you to write obviously risky things, such as

int *p = (int *)0xabcdef;

我々が、参照(Cでいうポインタ)がある言語を欲しがっているとする。原則によれば、不正な参照を許すのはよくないアイディアである。なぜならプログラマはそれを作り出してしまうから。実際に、ほとんどの最近デザインされた言語、たとえばJavaPythonでは、以下のような明らかに危険なことを書くことは許されていない。

int *p = (int *)0xabcdef;

Unfortunately, many designers have still not learnt that the special NULL pointer or null object is an equally bad idea. Python’s None, perl’s undef, and SQL’s NULL all fall in the same category. I can hear you list lots of advantages of having these. But stick to the principle: NULL is wrong because it causes horrible and tricky mistakes which appear even after the program was tested thoroughly. You cannot introduce NULL into the language and tell the programmer to be careful about it. The programmer is not capable of being careful! There is plenty of evidence to support this sad fact.

不幸にして、多くの設計者が、特別なNULLポインタやnullオブジェクトが同じように悪いアイディアである、ということを学んでいない。PythonのNone, Perlのundef, SQLのNULLはすべて同じカテゴリに入る。私は、あなたから、これらがあることによる利点をたくさん聞くことができるだろう。しかし、原則にこだわるなら、NULLは、それがプログラムを徹底的にテストした後でさえ発生する、恐ろしくかつやっかいな間違いを引き起こすという点において、間違っているのだ。あなたは、言語にNULLを取り入れ、プログラマに注意しろと言うことはできない。プログラマは注意深くあることなどできないからだ! この悲しい事実を支持する証拠は山ほどある。

Therefore NULL, null, None and undef must go. I shall collectively denote these with Python’s None. Of course, if we take away None, we must put something else back in. To see what is needed, consider the fact that None is intended as a special constant that signifies “missing value”. Problems occur when a given value could be either “proper” or “missing” and the programmer forgets to consider the case of missing value. The solution is to design the language in such a way that the programmer is always forced to consider both possibilities.

よって、NULL, null, None, undefは消え去るべきである。私は、これらをまとめてPythonのNoneで表そうと思う。もちろん、Noneを取り去ってしまうなら、我々は他の何かを導入しなければならない。何が必要かを見ていくには、Noneは、「欠損した値」を意味する特別な定数を意図しているとみなすことだ。与えられた値が「正しい」ことも「欠損している」こともあり、かつ、プログラマが欠損した値のケースの考慮を忘れたときに問題が発生する。解決策は、そのようなケースでは、プログラマに両方の値を考慮することを強制することだ。

For example, Haskell does this with the datatype Maybe, which has two kinds of values:
Nothing, meaning “missing value
Just x, meaning “the value is x“

たとえば、HaskellはこれをMaybeというデータ型で実現している。これは2種類の値を持つ。

Nothing, 欠損値を意味する。
Just x, 値がxであることを意味する

The only way to use such a value in Haskell is to consider both cases, otherwise the compiler complains. The language is forcing the programmer to do the right thing. Is this annoying? You will probably feel annoyed if you are used to ugly hacks with None, but a bit of experience will quickly convince you that the advantages easily outweigh your tendency for laziness. By the way, Haskell actually supports your laziness. Once you tell it that the type of a value is Maybe, it will find for you all the places where you need to be careful about Nothing. C, Java, Python, and perl stay silent and let you suffer through your own mistaken uses of NULL’s, null’s, None’s, and undef’s.

Haskellにおいては、このような値を扱う唯一の手段は、両方のケースを考慮することである。さもなければコンパイラが文句を言う。言語が、プログラマに正しいことをするように強制している。これは煩わしいことだろうか? もしあなたがNoneを使った醜いハックに慣れているのであれば、煩わしく感じられることだろう。しかし、ちょっと経験すれば、怠惰のためのあなたの性癖よりも利点の方がずっと上回っていることにすぐに納得するだろう。ところで、Haskellはまさにあなたの怠惰さを支援する。その値の型がMaybeであると一度言ってしまいさえすれば、HaskellはNothingについて気をつけるべき箇所すべてをあなたのために見つけてくれる。C, Java, Python, perlは、ずっとだまっていて、あなた自身がNULLやnullやNoneやundefを使って犯したミスであなたを苦しませることになる。

Other languages that let you have the data type like Haskell’s Maybe are ML and Ocaml because they have sum types. Pascal, Modula-2 and C have broken sum types because they require the programmer to handle the tag by hand.

HaskellのMaybeのようなデータ型を持つほかの言語は、MLやOcalmlである。なぜならこれらには「sum types」があるから。PascalやModula-2やCは壊れた「sum types」を持っている。なぜならそれらはプログラマにそのタグを手で操作することを強制するからだ*1

あらゆるものがオブジェクト(またはリストや配列)――Everything is an object (or list, or array)

Many languages are advertised as “simple” because in them everything is expressed with just a couple of basic concepts. Lisp and scheme programmers proudly represent all sorts of data with conses and lists. Fortran programmers implement linked lists and trees with arrays. In Java and Python “everything is an object”, more or less.

多くの言語は、その言語ではすべてが少数の基本的な概念で表現できるからシンプルである、と宣伝されている。Lispschemeプログラマは誇らしげにあらゆる種類のデータをコンスセルとリストで表現する。Fortranプログラマは連結リストやツリーを配列で実装する。JavaPythonでは、「あらゆるものがオブジェクト」だ(まあだいたいは)。

It is good to have a simple language, but it is not good to sacrifice its expressiveness to the point where most of the time the programmer has to encode the concepts that he really needs indirectly with those available in the language. Programmers cannot do such things reliably, and the compiler cannot help them with the task because it does not know what is in programmer’s head.

シンプルな言語を持つのは良いことだ。しかし、表現力を犠牲にし、プログラマが本当に必要とする概念をコード化しようとする際に、言語で可能な機能では間接的にしか表現できないのなら、それはよくないことだ。プログラマはそんなことを確実に行うことはできないし、コンパイラもそれを助けることはできない。なぜならコンパイラプログラマの頭の中にあることを知らないからだ。

Let us look at a typical example in scheme. Suppose we would like to represent binary trees in which the nodes are labeled with integers. In scheme we might do this by representing the empty tree as (), and use a three-element list (k l r) to represent a tree whose root is labeled by k, the left subtree is l, and the right subtree is r. A quick search on Google shows that this is a popular way of implementing trees in scheme. It’s simple, it’s cool, it’s easy to explain to the students, but scheme will have no idea whatsoever what you’re doing. There are a number of trivial mistakes which can be made with such a representation, and scheme won’t detect them (at best you will get a runtime error): you might write (l k r) instead of (k l r), you might mistakenly pass a four-element list to a function expecting a tree, you might mistakenly think that the integer 42 is a valid representation of the tree (42 () ()), you might mistakenly try to compute the left subtree of the empty tree, etc. And remember, the programmer will make all these mistakes.

schemeにおける典型的な例を示す。我々が、各ノードが整数でラベル付けされる二分木を表現したかったとする。schemeでは、我々はこう表現するだろう。空の木は()とし、そして3つの要素を持つリスト(k l r)により、そのルートがkでラベルされ、左の部分木をl、右の部分木をrとする木を表現する。Googleでちょっと検索してみると、これはschemeにおける木の実装としてポピュラーなものだ。シンプルで、クールで、学生に説明しやすい。しかし、schemeは、あなたがやろうとしていることについて何一つ知らない。この表現方法には、犯しうる些細な間違いがたくさんあり、schemeはそれを検出できない(よくてもあなたはランタイムエラーを受け取るだけだろう)。あなたは(k l r)の代わりに(l k r)と書いてしまうかもしれないし、ツリーを期待している関数に間違って4つの要素のリストを渡してしまうかもしれないし、整数の42が、(42 () ())という木の正しい表現だと思ってしまうかもしれないし、間違って空のツリーの左の部分木を計算しようとするかもしれないし、等々。そして、思い出そう。プログラマとはこれらのあらゆる間違いを犯してしまうものなのだ。

With objects the situation is somewhat better. In Java we would define a class Tree with three attributes root, left, and right. It will be impossible to build a tree with a missing attribute, or too many attributes. But we will hit another problem: how to represent the empty tree? There are several choices none of which is ideal:

オブジェクトがあれば状況はいくぶんよくなる。Javaでは、我々はroot, left, rightの3つの属性を持つTreeクラスを定義するだろう。欠けた属性、多すぎる属性を持つ持つ木は作れない。しかし、我々は別の問題に突き当たる。空の木はどう表現すべきだろうか? どれも理想的ではないが、いくつかの方法がある。

  1. the empty tree is null: this is the worst solution, as any Java programmer knows
  2. we define a class Tree and subclasses EmptyTree and NodeTree represent the two different kinds of tree
  3. we add a fourth attribute empty of type boolean which tells us whether the tree is empty
  1. 空の木はnullとする:これは、どんなJavaプログラマでも知っているように、最悪の解決方法である。
  2. Treeクラスと、2種の異なる木(のノード)を表現するため、サブクラスEmptyTreeとNodeTreeを定義する。
  3. 4つ目のboolean型の属性emptyを追加し、それにより木が空であることを示す。

There are probably other options. The first solution is horrible, as every Java programmer knows, because it leads to many NullPointerExceptions. The second solution is probably the most “object-orientedly correct” but people find it impractical, as it spreads code around in two classes. When I taught java I lectured the third solution, but that one has the big disadvantage that the programmer is responsible for checking every time whether a tree is empty or not.

おそらく他にも選択肢はあるだろう。最初の解決方法は、すべてのJavaプログラマが知っているようにひどいものだ。なぜなら多くのNullPointerExceptionを引き起こすから。2番目の解決方法はおそらくもっとも「オブジェクト指向的に正しい」が、人々はそれが実用的でないことを知っている。コードが2箇所に分散してしまうからだ。私がJavaを教えたときは3番目の解決方法を教えたが、この方法は、プログラマが毎回木が空かそうでないかを判定する責任を負うという大きな欠点がある。

A decent programming language should help with the following common problems regarding binary trees:

まともなプログラミング言語は、二分木に対する以下のよくある問題について、何らかの助けをするべきだ。

  1. Prevent the construction of an invalid tree, such as one with missing parts, or dangling pointers.
  2. Prevent at compile time access to a component which is not there. For example, the compiler should detect the fact that the programmer is trying to access the left subtree of the empty tree.
  3. Make sure the programmer never forgets to consider both possibilities - the empty tree and the non-empty tree.
  1. 不正な木を作ってしまうことを防止する。部分が欠けていたり、ダングリングポインタのような。
  2. 存在しない要素に対するアクセスを、コンパイル時に防止する。たとえば、コンパイラプログラマが空の木の左の部分木にアクセスしようとしていることを検出すべきである。
  3. プログラマが、空の木、空でない木の両方の可能性について考慮することを決して忘れないようにする。

The above scheme representation does not help with the first problem. A C implementation with pointers would allow dangling pointers. An object-oriented solution typically won’t help with the second and the third problems.

上のschemeの表現は、最初の問題について助けになってくれない。Cのポインタによる実装はダングリングポインタを許す。オブジェクト指向の解決法は2番目と3番目の問題について通常は助けになってくれない。

You might wonder what it is that I want. The answer is that the programming language should have built-in inductive data types, because that’s what binary trees are. In Haskell, which has inductive data types, trees are defined directly in terms of their structure:

data Tree = Empty | Node Int Tree Tree

あなたは私が欲しているものが何か不思議に思うかもしれない。回答は、プログラミング言語は組み込みのinductive data型を持つべきだ。なぜなら二分木がそうであるから。inductive data型を持つHaskellなら、木は言語の機構を使って直接に定義できる*2

data Tree = Empty | Node Int Tree Tree

This expresses the definition of trees directly: a tree is either empty or a node composed of an integer and two trees. Haskell will be able to catch all the common problems listed above. Other languages supporting this sort of definition are ML, Ocaml, F#, and interestingly Visual Prolog (I am told by Wikipedia).

これは、木の定義を直接に表現している。木は、空または整数とふたつの木から成るかもしれない。Haskellは上であげたすべてのよくある問題を検出できる。このような定義をサポートするほかの言語はMLとOcamlとF#、そして面白いことにVisual Prologである(私はこれをWikipediaで知った)。

We might ask for more. Suppose we wanted to implement binary search trees. Then we would require that the left subtree only contains nodes that are smaller than the root, and the right subtree only nodes that are larger than the root. Can a programming language be designed so that this property is guaranteed? Yes, for example the compiler could insert suitable checks into the code so that anomalies are detected during execution as soon as they occur. This might be nice for debugging purposes, but what is production code supposed to do if it discovers an anomalous data structure during its execution? Ignore it? Raise an exception? It is much more useful to know before the program is run that the data structure will never be corrupted. Here we hit against a law of nature: there is no algorithm that would analyze an arbitrary piece of code and determine whether it will only produce valid search trees. It is a fact of life. If you really want to check that your programs are correct you will have to help the compiler. There are excellent tools for doing that, such as Coq and Agda―have a look to see how programmers might develop their code in the future.

我々はさらに求めるだろう。二分探索木が欲しいとする。すると我々は左の部分木はルートより小さなノードだけを含み、右の部分木はルートよりも大きいノードだけを含むことを要求する。プログラミング言語を、この特性が保証されるように設計することはできるだろうか? イエス。たとえば、コンパイラは、実行中、異常が発生後即座に検出できるよう、チェックするコードを挿入することができる。これはデバッグ目的に良いかもしれないが、製品版のコードでは、実行中に異常なデータ構造を発見したらどうすべきだろうか? 無視する? 例外を投げる? 実行前に知ることができ、データ構造が決して壊れなければ、そのほうがずっと役に立つ。ここで、我々は、自然の法則に突き当たる。ある任意のコードを分析して、それが必ず正しい探索木しか生成しないのかどうかを決定するアルゴリズムは存在しない。これは動かすことのできない現実である。もしあなたが本当に自分のプログラムが正しいことをチェックしたかったら、あなたがコンパイラの手助けをしなければならない。それをするためのすばらしいツールがある――CoqとかAgdaとか。見て、プログラマが将来どうコードを開発するようになるかを見るといい。

定義と変数の混同――Confusing definitions and variables

A definition binds an identifier to a particular fixed value. A variable or a mutable value is a memory location which holds a value that can be read and changed. These two notions should not be confused. Unfortunately, traditional programming languages only provide variables, so many programmers don’t even understand what definitions are. Java tries to fix this with the final declaration, and C++ with the const declaration, but these are not used by programmers as much as they could be (which is a typical sign of dubious design decisions).

定義は、ある識別子を特定の決まった値に束縛する。変数すなわち変更可能な値は、読み書きできる値を保持したメモリ領域である。このふたつの概念は混同すべきではない。不幸にして、伝統的なプログラミング言語は変数だけを提供してきた。そのため、多くのプログラマは定義とは何であるかを理解さえしていない。Javaはfinal宣言でこれを修正しようとし、C++はconst宣言でそうしようとしたが、それは、実際にそれが使える箇所ほどには使われていない(これは疑わしい設計上の決定の典型的な兆候である)。

Using variables instead of definitions is wrong for a number of reasons. First, if the compiler knows which identifiers are bound to immutable values it can optimize the code better. It can, for example, decide to store the value in a register, or to keep around several copies without worrying about synchronization between them (think threaded applications). Second, if we allow the programmer to change a value which is supposed to be constant, then he will do so.

定義の代わりに変数を使うのはいくつもの理由で間違いだ。第一に、コンパイラがどの識別子がimmutableな値となるかを知っていれば、コードをより最適化できる。たとえば、値をレジスタに格納したり、同期を気にすることなくいくつかのコピーを作ることができるだろう(スレッド分割されたアプリケーションを考えよ)。第二に、もし我々が、本来定数であるはずだった値を変更することを許してしまったら、プログラマはきっとそれをやってしまうだろう。

If you observe how variables are typically used, you will see several distinct uses:

もしあなたが変数が典型的にどう使われるのかを観察したら、いくつかのまったく異なる用途を見ることになるだろう。

  • often a variable is only assigned to once and is used as an (immutable) definition
  • a variable in a loop or list comprehension ranges over the elements of a list, or a collection of objects
  • a variable stores the current state and is genuinely mutable
  • しばしば変数は1回しか代入されず、(immutableな)定義として使用される。
  • ループやリストの中の変数は、リストの各要素やオブジェクトのコレクションの中を動き回る。
  • ある変数は、現在の状態を保持し、それが真のmutableだ。

Should loop counters be mutable? I think not. Code that changes the loop counter in the body of the loop is confusing and error prone. If you want to fiddle with counters, use the while loop instead. So in two out of three cases we want our variables to be immutable, but the popular programming languages only give us variables. That’s silly. We should design the language so that the default case is an immutable value. If the programmer wants a mutable value, he should say so explicitly. This is just the opposite of what Java and C++ do. An example of a language that is designed this way is ML and ocaml. In Haskell you have to jump through hoops to get mutable values (now I am going to hear it from a monad aficionado, please spare me an unnecessary burst of anger).

ループカウンタはmutableであるべきか? 私はそうは思わない。ループカウンタをループのボディの中で変更するようなコードは混乱の元だしエラーを起こしやすい。もしあなたがループカウンタをいじくりたいのなら、代わりにwhileループを使うべきだ。よって、3つのうち2つのケースでは、我々は変数をimmutableとして使いたいのだ。なのにポピュラーなプログラミング言語は変数しか与えてくれない。これはおろかなことだ。我々は、デフォルトがimmutableになるように言語を設計すべきなのだ。もしプログラマが変更可能な値が欲しければ、明示的に指定すべきだ。これはJavaC++がやっていることと反対である。この方法で設計されている言語の例は、MLとocamlだ。Haskellでは、あなたは変更可能な値を売るにはタガを飛び越えなければならない。(今からmonadの熱烈なファンからそれを聞くつもりなので、不要な怒りの炸裂は勘弁して欲しい)。

スコープ外の変数――Out of scope variables

I thought I would not have to explain why undefined identifiers are a bad a idea, but the reader in comment 27 explicitly asked about this.

私は、未定義の変数が悪いアイディアであるということは説明不要だと考えたが、コメント27の読者は明示的にそれについて尋ねてきた。

If a programmer refers to an undefined name then an error should be reported. Concretely, I claimed that Python should complain about the following definition:

def f(n): return i + n

プログラマが未定義の名前を参照したら、エラーが報告されるべきだ。具体的には、私はPythonは以下の定義に文句を言うべきだと主張しているのだ。

def f(n): return i + n

What is i? Pythonists will quickly point out that i will be defined later, and how deferred definitions are useful because they allows us to define mutually recursive functions. Indeed, Java and Haskell also accept mutually recursive definitions. But unlike Python they make sure that nothing is missing at the time of definition, whereas Python will only complain when the above function f is used. To be honest, Python kindly displays the correct error message showing that the trouble is with the definition of f. But why should this be a runtime error when the mistake can easily be detected at compile time? Actually, this question leads to a more general question, which I consider next.

iって何だ? Pythonistは即座に、iは後で定義されるし、遅延した定義が相互に再帰する関数を定義することを許すのでいかに役に立つか、ということを指摘するだろう。実際にはJavaHaskellも相互に再帰する定義を許す。しかし、Pythonとは異なり、それらは定義の時点で何も欠けていないことを確認する。それに反してPythonは上の関数fが使用される時にしか文句を言わない。正直なところ、Pythonは、親切にも問題がfの定義にあるという正しいエラーメッセージを表示する。しかし、間違いが簡単にコンパイル時に検出できるとき、なぜランタイムエラーにするべきなのか? この疑問は、より一般的な疑問につながる。私は次にそれについて考える。

ミスはいつ発見されるべきか?――When should mistakes be discovered?

Should programming bugs be discovered by the programmer or by the user? The answer seems pretty clear. Therefore, a language should be designed so that as many programming errors as possible are discovered early on, that is before the program is sent to the user. In fact, in order to speed up development (remember that the development time is expensive) the programmer should be told about errors without having to run the program and directing its execution to the place where the latest code change actually gets executed.

プログラミングバグは、プログラマかユーザか、どちらによって発見されるべきか? この答えは実に明白に見える。よって、言語は可能な限りたくさんのプログラミングエラーが早期に発見されるようにデザインされているべきである。プログラムがユーザの手元に送られる前に。実際には、開発をスピードアップするには(開発時間が高価であったことを思い出そう)、プログラマは、プログラムを実行する必要なしにエラーについて教えられるべきであり、その実行は最後のコード修正がまさに実行される場所に向けられるべきだ。

This philosophy leads to the design of statically checked languages. A typical feature of such a language is that all the types are known at compile time. In contrast, a dynamically typed languages checks the types during runtime. Java, Haskell and ocaml are of the former kind, scheme, javascript and Python of the latter.

この哲学は、静的チェックのある言語の設計につながる。このような言語の典型的特徴は、コンパイル時にすべての型がわかっていることである。対照的に、動的型付け言語は型を実行時にチェックする。Haskellocamlは前者であり、schemejavascriptPythonは後者だ。

There are situations in which a statically checked language is better, for example if you’re writing a program that will control a laser during eye surgery. But there are also situations in which maximum flexibility is required of a program, for example programs that are embedded in web pages. The web as we know it would not exist if every javascript error caused the browser to reject the entire web page (try finding a page on a major web site that does not have any javascript errors).

静的にチェックされる言語の方が望ましい状況は存在する。たとえば、あなたが目の手術に使うレーザーを制御するプログラムを書いている場合だ。しかし、プログラムに最大限の柔軟性が要求される状況もある。たとえばWebページに埋め込まれているプログラムだ。あなたも知っているように、あらゆるjavascriptエラーが起きるたびにWebページ全体をブラウザが拒否していたら(javascriptのエラーをまったく持たないメジャーなWebサイトを見つけてみたまえ)、Webページは存在していないことだろう。

Let me also point out that testing cannot replace good language design. Testing is very important, but it should be used to discover problems that cannot be discovered earlier in the development cycle.

また、テストが良い言語設計を代用することはできないということも指摘させてほしい。テストはとても重要だが、それは、開発サイクル上、より早く発見することができない問題を発見するために使われるべきだ。

I used to think that statically checked languages are better for teaching because they prevent the students from doing obviously stupid things. About two years ago I changed my mind. The students learn much better by doing stupid things than by being told by an oppressive compiler that their programs are stupid. So this year I switched to Python. The students are happier, and so am I (because I don’t have to explain that code must be properly indented). Python does not whine all the time. Instead it lets them explore the possibilities, and by discovering which ones crash their programs they seem to understand better how the machine works.

私は、静的型付け言語は、学生が明らかに馬鹿なことをしでかすことを防ぐので、教えるのによいと以前は思っていた。2年ほど前私は考えを変えた。学生は、やたらと厳しいコンパイラに自分のプログラムが馬鹿だと言われることよりも、実際に馬鹿なことをやらかすことからずっと多くのことを学ぶ。そこで今年から私はPythonに切り替えた。学生はより幸福になり、私もまたそうだ(なぜならコードを適切にインデントしなければならないということを説明する必要がないので)。Pythonはのべつ幕なしに文句を言ったりしない。代わりに彼らに可能性を探らせてくれる。何が彼らのプログラムをクラッシュさせたのかを発見することで、彼らはマシンがどう動くのかをよりよく理解しているようだ。

*1:「sum types」が何かわかりませんでした…… えらいひと教えてください(_o_)

*2:inductive data typeもわかりませんでした……orz。こちらについてもえらいひと教えてください(_o_)