YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

"Enterprise Roadmap to SRE"の日本語訳が出ました

はじめに

こんにちは、Google CloudでオブザーバビリティとSREの担当をしているものです。今日は去年仕事でやってたものがようやっと表にでたのでその紹介をします。

「SREエンタープライズロードマップ」がでました

「SREエンタープライズロードマップ」はかねてより "Enterprise Roadmap to SRE" としてGoogle SREのウェブサイトで公開されていたレポートの日本語訳です。無料で公開されています。

googlesre.page.link

(英語になってしまう人は画面右下の言語設定を「日本語」に設定してみてください)

このレポートは役員レベルを始めとする意思決定者の方々でSREに関心が少なからずあり、自組織でどのように適用していくかについて、手早く理解したいという方々を想定読者としています。

このようなトピックを見て気になった方はぜひご一読ください。60ページ程度の小冊子ですので、時間のない方でもさっと読める分量だと思います。

さらに理解を深めたい方に

本レポートはSREの概要を理解するのには向いていますが、ここからさらに詳細を理解したいという方々には以下の書籍をおすすめいたします。

まずは本レポートの中でも参照されているSRE本こと「SRE サイトリライアビリティエンジニアリング」と、その演習向け書籍となる「サイトリライアビリティワークブック」です。

特に1冊目は書籍内で触れられている各プラクティスが共通言語として参照されることが多いので、通読はしなくても手の届くところにご用意いただくことをおすすめします。

次は明日発売開始の「オブザーバビリティ・エンジニアリング」です。

こちらは特にサービスレベル目標を決めた後に、実際に計装を行ったり、テレメトリーを分析するための基盤を考えたい、という方向けの書籍です。

また既存の監視システムからよりSREを目指した方向に発展させていきたい、という方には「入門 監視」をおすすめします。

この本は直接的にSREという名前は出していませんが、目指す方向性は一致した内容になっていますので、非常に参考になると思います。

本レポートの最後にあるGoogle以外でのSRE導入の事例をもっと見たいという方には「SREの探求」をおすすめします。各社事情が異なる状況からSREの導入を行い、どのようにその運用に至ったかを知る、良い書籍だと思います。

C言語でプログラミングする際の覚書(Notes on Programming in C)

はじめに

こんにちは、Go界のシャールト・コプリーです。気がついたら最後のエントリから3ヶ月も経ってました。

Goを始めると「なんでこういう書き方になってるんだろう」とか、「そもそもなんでこういう仕様になってるんだろう」とか思うことがちらほらあると思います。これは大いにGoの作者の一人であるRob Pike氏の思想に依るところがあるのが見受けられます。彼のプログラムに対する考え方が25年前に公開され「Pike Style」として知られていますが、いまもその考え方は大きくは変わっていないと思われます。せっかくなので翻訳しました。本文はC言語に関する文章ですがその本質は言語に依らないものだと思います。

(追記)25年前なのでコンパイラの動作に依存する部分(includeに関する記述)などは古い部分もありますが、プログラミングスタイルに関する部分は現代にも通じるところがあると思います。

(追記2)幾つか誤訳をご指摘いただけたので修正いたしました。コメントに感謝しています。

(追記3)僕の役はひどいのでこちらを読みましょう。

誤訳箇所の一覧です。

C言語でプログラミングする際の覚書

Rob Pike 1989年2月21日

Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights Reserved.

まえがき

KernighanとPlaugerの “The Elements of Programming Style” (「プログラム書法」木村泉訳)は重要で間違いなく影響力のある書籍でした。しかし、ときに私はその簡潔なルールが本来意図した哲学の簡潔な表現としてではなく、良いスタイルにするためのクックブック的な手法として捉えられていると感じます。もし、かの書籍が変数名は意味があるように選ばれるべきだと主張するのであれば、変数名はその用途を説明したエッセイになっている方が良いということにならないでしょうか。 MaximumValueUntilOverflowmaxval よりも良い変数名でしょうか。私はそうは思いません。

従うべきは、厳しいルールを与えることではなく、プログラムを書く際の明確さの哲学を全体として助長するような短いエッセイです。みなさんにこれらすべてに同意してもらいたいわけではありません。なぜならこれは意見であり、意見は時間とともに変化するものだからです。しかし、私の意見は頭のなかにしばらくあったものをまとめたものであり、長らく文章として公開してきませんでした。またこれらは私の多くの経験を踏まえたものです。そのため、プログラムの細かな部分に関する計画方法の理解に役立てば幸いです。(これまでプログラム全体の計画に関しての良い文章は読んだことがありますが、それらはこの文章で触れる内容の一部となります)もしいまから紹介するものを特異だと感じたのであれば、それはそれで結構です。また同意できないとしても、それも結構です。しかし、なぜあなたがなぜ同意できないのかを考えるきっかけになったのであれば、より良いことでしょう。どのような状況においても、私がそういったからという理由で私が言ったやり方で書くべきではありません。あなたがそのプログラムで実現したいものを最も良く記述できると考える方法でプログラムしてください。そしてそれを一貫し容赦することなく行ってください。

あなたのコメントをお待ちしています。

表示の問題

プログラムは一種の出版です。プログラムはプログラマや他のプログラマ(おそらく数日後、数週間後、数年後のあなた自身)、そして最終的にはマシンに読まれることを前提としています。マシンはそのプログラムがどれほど美しいかは気にしません。コンパイルできれば、それで幸せなのです。しかし、人間は気にします。そして気にすべきです。ときに気にし過ぎます。たとえば、pretty printerは機械的にプログラムの重要でない部分も強調するような美しい出力をします。これは、英語の文章内にある前置詞 すべて太字 するの 同様 です 。しかしながら多くの人々がプログラムはAlgol-68 Reportのような見た目(システムによってはそのスタイルで編集することを強制します)であるべきと考えますが、明瞭なプログラムはそのような見た目で成されるものではなく、また悪いプログラムであれば明瞭にしても滑稽になるだけです。

もちろん表示に関する一貫した規約は見た目を明瞭にするためには重要です。インデントは最もよく知られ最も有用な例です。しかし、印字がプログラムの意図を曖昧なものにしてしまうのであれば、がやり過ぎだったということです。したがって、たとえあなたが昔ながらのタイプライターのような飾り気のない表示にこだわっているのだとしても、表示における愚かさを意識しましょう。余計な飾りをやめましょう。たとえばコメントは短く、バナーは無くしましょう。プログラム内で言うべきことを、綺麗に一貫して言いましょう。それから先に進みましょう。

変数名

そうですね、変数名です。変数名の長さは美徳ではありません。表現の簡潔さが大事なのです。滅多に使われないグローバル変数は長い名前にしてもいいでしょう。たとえば maxphysaddr といった具合です。ループ内のすべての行に出現する配列のインデックスは i 以上に凝った名前を付ける必要はないでしょう。たとえば indexelementnumber といった変数名はタイプ量(あるいはエディタ内で呼ばれる回数)が増え、計算の内容を不明瞭にします。変数名が巨大な場合、何が起きているかを把握するのが難しくなります。これは表示に関する問題の一部です。

for(i=0 to 100)
    array[i]=0

というコードと

for(elementnumber=0 to 100)
    array[elementnumber]=0;

というコードを見比べてみましょう。

現実の例では問題はもっと速く明らかになります。インデックスはただの注記なので、そのように扱いましょう。

ポインタもまた気が利いた注記が必要です。どの np が "node pointer" を意味しているかがすぐに分かる命名規則を一貫して使っていれば、 npnodepointer という名前と同じくらい読みやすいものになります。

プログラムの可読性に関していえば、命名において一貫性は重要です。ある変数に maxphysaddr と名づけたら、同様の変数に lowestaddress という名前をつけてはいけません。

最後に、私は最短の名前ではなく最も情報量がある名前を好み、ほかは文脈に任せる方法を好んでいます。たとえば、グローバル変数は典型的には使われる際にはほとんど文脈がないので、名前は比較的それだけで内容がわかるものである必要があります。したがって、グローバル変数名には maxpysaddrMaximumPhysicalAddress ではありません)という名前をつけ、ローカル変数には NodePointer ではなく np のような名前をつけます。これは好みによるところが大きいですが、その好みというのは明瞭さに関係するものです。

名前に大文字を入れるのも避けています。散文調の文章を見慣れた私には、大文字が文章の中に入ると不格好に感じられ快適に読めないのです。大文字は悪い印刷のように目障りなのです。

ポインタの使用

C言語はポインタでなんでも指せるという点で特異です。ポインタは賢い道具で、他の同様の道具のように、上手に使えば楽しく生産的になりえるものの、使い方を間違えると大きな損害を与えます。(この文章を書く数日前に木工彫刻刀で親指を怪我したばかりです。)ポインタは危険すぎる、あるいは汚すぎると考えられ、学術会では評判が良くありません。しかし私はポインタは強力な 注記 であると考えます。つまり、ポインタは明瞭な表現をする手助けをしてくれるという意味です。

次の状況を考えてみてください。あるオブジェクトに対するポインタがあるとき、それはまさにそのオブジェクトに対する名前であり、他のなにものでもありません。些細なことのように思えるかもしれませんが、次の2つの表現を見てください。

np
node[i]

最初のポインタはノードを指していて、2番目は(たとえば)同じノードを評価しています。しかし、2番目の形式は式です。単純ではありません。これを理解するためには node が何か、 i が何かを理解し、そして nodei がどのような関係にあるか(おそらく明記されていない)を周りのプログラムから理解しなければなりません。独立した式それだけでは inode の正しいインデックスかはわからないですし、ましてそれが私達がほしい要素のインデックスかはわかりません。もし ijk のすべてがノードの配列のインデックスだった場合、容易にうっかりと間違えてしまいます。特にサブルーチンに渡すときは間違いを起こしやすく、コンパイラでは避けようがありません。ポインタであれば1つのものを指しています。配列とインデックスは受け取ったサブルーチン側がお互い関係があるものとして信用しなければなりません。

オブジェクトに評価する式は、オブジェクトのアドレスよりも本質的により判断しづらく間違いを起こしやすいものです。ポインタを正しく使うことでコードを簡潔にできます。

parent->link[i].type

lp->type

を見比べてください。

もし次の要素の型が必要な場合は

parent->link[++i].type

よりも

(++lp)->type

のほうが見やすいでしょう。

i は値が進みますが、それ以外はなにも変化がありません。ポインタであれば進めるのは1つのものだけです。

ここでも表示の問題が出てきます。ポインタを使って構造を読み込むのは式を読むよりも理解が楽です。必要なインクの量は少なく、コンパイラやコンピュータが展開する労力も減ります。関連した問題として、ポインタの型が正しく使われているかに影響を与えているかというものがあります。これでコンパイル時に配列のインデックスが適切で無い旨のエラー検出が可能になります。また、そのオブジェクトが構造体の場合はタグとフィールドは型を思い出させてくれます。つまり

np->left

はそれぞれが何を挿すかを思い出させるのに十分です。これがもしインデックスが指定された配列であれば、配列名はきちんと選ばれた名前で、表現は長くなります。

node[i].left

再度になりますが、余計な文字は表現が大きくなるにつれて苛立たしくなります。

ルールとして、もし同様の表現を含むコード、たとえばデータ構造の要素を評価するような複雑なデータ構造があった場合、思慮深くポインタを使えばすっきりとできます。

if(goleft)
     p->left=p->right->left;
else
     p->right=p->left->right;

p の複合的な使い方をしているこのコードが何をしているかを考えてみましょう。時には一時変数(この場合は p )を使用したり、計算の本質を抜き出すマクロを使用する価値があります。

プロシージャ名

プロシージャ名はそれが何をするかを反映すべきです。つまり関数名はそれが何を 返すか を反映すべきです。関数は式の中で使われ、しばしば if の中で使われます。したがって適切に読む必要があります。

if(checksize(x))

という記述は役に立ちません。なぜならchecksize関数がエラーの時にtrueを返すのか、エラーでない時にtrueを返すのかが推測できないからです。代わりに

if(validsize(x))

とすれば要点が明らかになり、将来同じ関数を使うときの間違いが起こりにくくなるでしょう。

コメント

慎重に、経験と判断をもって書く必要があります。私は、いくつかの理由から、コメントを極力書かないようにしています。まず、コードがきれいで、良い型名や変数名を使っている場合、コードそれ自身が内容を説明しているはずです。次に、コメントはコンパイラにはチェックされないので、コメントが正しいという保証はありません。特にコードが変更されたあとはそうです。誤解を招くコメントは非常に紛らわしいものです。最後に、表示の問題です。コメントはコードを散乱させてしまいます。

しかし私も時々コメントを書きます。ほとんどもっぱらその後に何が続くかを説明するために書きます。例えば、グローバル変数の使い方とその型の紹介(大きなプログラムではいつもコメントするものの一つ)、通常の使い方をしていないプロシージャや非常に重要なプロシージャの紹介、あるいは大きな計算をする際の区切りなどです。

悪いコメントの例としては次のようなものがあります。

i=i+1; /* Add one to i */

さらに悪いコメントの例は

/**********************************
*                                 *
*           Add one to i          *
*                                 *
**********************************/

i=i+1;

いまこの例を笑ってはいけません。実際にこのようなコードに出くわしたらはじめてそこで笑いましょう。

複雑さ

たいていのプログラムは複雑過ぎます。つまり、問題を効率的に解決するために必要な複雑さよりも複雑なのです。なぜでしょう。多くの場合、それは設計の悪さに起因しますが、ここではその話は大きすぎるテーマなので触れません。しかし、プログラムはしばしば細かなレベルでも複雑で、ここではその点についてお話します。

ルール1 プログラムのどこに時間がかかる場所があるかはわからない。ボトルネックは思わぬ場所で起きる。したがって、どこがボトルネックかわかるまでは、合理的な判断なしに余計な勘ぐりをしたり高速化のためのハックをするのはやめよう。

ルール2 計測しよう。計測するまでは高速化のためのチューニングはやめよう。さらに、高速化のためだとしても、プログラムの残りの部分を圧倒するような場合でない限り、チューニングはやめよう。

ルール3 n が小さい時にはしゃれたアルゴリズムは遅く、そして通常 n は小さい。しゃれたアルゴリズムではその影響が現れるコンスタントな値は大きい。 n が頻繁に大きくなるとわかるまでは、手が込んだアルゴリズムは使わないようにしよう。(たとえ n が大きくなるとしても、まずはルール2を考えよう)たとえば、日常的な問題では二分木は常にスプレー木よりも速い。

ルール4 しゃれたアルゴリズムは単純なアルゴリズムよりもバグを起こしやすい。そして実装がずっと難しい。単純なアルゴリズムと単純なデータ構造を使おう。

次に挙げるものはほぼすべての実用的な問題を解決するデータ構造の一覧です。

  • 配列
  • 連結リスト
  • ハッシュテーブル
  • 二分木

もちろん、これらのデータ構造を組み合わせたデータ構造にすることも配慮しなければいけません。たとえば、シンボルテーブルは文字型の配列の連結リストを含んだハッシュテーブルとして実装されているでしょう。

ルール5 データが支配する。正しいデータ構造を選び、物事をうまくまとめれば、それを使うアルゴリズムは自明である。アルゴリズムではなく、データ構造がプログラムの中心である。(詳しくは「人月の神話」を参照のこと)

ルール6 ルール6はない。

データでプログラミングする

アルゴリズム、つまりアルゴリズムの細かな部分は、しばしばデータという簡潔で、効率的で、表現豊かな形に記号化されます。それは、たとえば、多くのif文という形ではありません。その理由は、もし手元にある 複雑さ が独立した細かな部分の組み合わせによるものなのであれば、それは 記号化できるから です。古典的な例で言えば、表のパースです。これはプログラミング言語の文法を、定形のかなり単純なコード片によって説明可能な形式に記号化することです。特にこのような問題に取り組むときには有限状態機械が採用されていますが、ある抽象的な入力を「パース」して一連の独立した「振る舞い」に変換するようなあらゆるプログラムは、ほとんどが生産的な形としてデータ駆動のアルゴリズムになります。

おそらくこのような設計の最も魅力的な側面は、ときどき表が、古典的な例ではパーサジェネレータといった、他のプログラムに生成されることです。もっと現実的な例では、オペレーションシステムが、I/Oのリクエストとそれに対する適切なデバイスドライバの組み合わせを持ったひとまとまりの表によって動いているとした場合、システムはマシンに接続されたある特定のデバイスの説明を読み込み、対応する表を表示するようなプログラムによって「構成される」でしょう。

データ駆動プログラムが一般的でない理由の一つは、少なくとも初心者においては、Pascalによる圧政でしょう。Pascalは、その創始者のように、コードとデータの確固たる分離を信条としています。したがって(少なくとも本来の形では)初期化されたデータを作ることはできません。このことはチューリングフォン・ノイマンの理論の前にはたち消えてしまいます。この理論はストアドプログラム方式のコンピュータの基本原理を定義しています。コードとデータは同じものです。あるいは少なくとも同じになりえます。それ以外にどうやってコンパイラが動作するかを説明できるでしょうか。(関数型プログラミング言語でもI/Oにおいて同様の問題を抱えています。)

関数ポインタ

Pascalの圧政は、初心者が関数ポインタを使わない、という状況ももたらしています。(Pascalでは関数を値にもった変数を持つことができません。)複雑さを記号化するために関数ポインタを使うことにはいくつか面白い特性があります。

複雑さのいくつかは参照先のルーチンに渡されます。そのルーチンはいくつかの標準的な規約に従わなければいけません。そのルーチンは同じ呼び出し方をされたひとまとまりのルーチンの一つです。しかしそれ以上にそのルーチンは自分がすべきことしかしません。複雑さが 分散された のです。

この規約という考え方があり、そこでは似たような使われ方をする関数はすべて似たような振る舞いをしなければいけなません。これによって、ドキュメントが書きやすくなり、テストがしやすくなり、コードを成長しやすくし、さらにはプログラムをネットワーク越しに分散させて稼働させやすくなります。この規約はリモートプロシージャコールとして記号化されます。

私は、関数ポインタを明確な形でつかうことがオブジェクト指向プログラミングの心であると、主張します。あるデータに対してひとまとまりの操作を行いたい、かつ、それらの操作に対してひとまとまりのデータ型を返したい場合、そうプログラムする簡単な方法は各型に対して関数ポインタをまとめることです。これは、一言で言えば、クラスとメソッドを定義することです。もちろん、オブジェクト指向言語は優れた構文、派生型などといった、より多くのものを提供してくれます。しかし、概念的にはオブジェクト指向言語は先のものにほんの少しのおまけを提供してくれるだけです。

データ駆動プログラムと関数ポインタを組み合わせが、プログラムの驚くほど表現豊かな書き方へと導いてくれます。私の経験から、その書き方はしばしば嬉しい喜びももたらしてくれます。たとえ特別なオブジェクト指向言語がなくても、余計な手間をかけずに、オブジェクト指向の90%の恩恵を授かることができ、結果をより管理することができるようになります。より高度な次元の実装方法を推奨することはできません。私がこのようなやり方で書いてきたすべてのプログラムは、その後の多くの開発のあとも快適に動作しています。規律が少ない手法で開発されたものよりもずっと良く動作しています。以上です。この手法が強制する規律は、長い目で見て大きな利益をもたらします。

インクルードファイル

単純なルールです。インクルードファイルは決してインクルードファイルをインクルードすべきではありません。代わりに(コメントや暗黙的に)まずどのファイルがインクルードされるべきか宣言した場合、どのファイルをインクルードするかという問題はユーザ(プログラマ)側に押し付けられますが、ある意味では管理がしやすくなり、ビルド時に複数のインクルードを避けることができます。複数のインクルードはシステムプログラミングでの悩みの種です。一つのCのソースファイルをコンパイルするのに5回以上もインクルードされたファイルがあることは珍しくありません。Unix/usr/include/sys はこの点でひどいものです。

1つのファイルが2度呼ばれないように #ifdef を駆使する方法がありますが、普通は間違った結果となります。 #ifdef はインクルードされるファイルの中にあり、インクルードする側にはありません。その結果、しばしば何千行もの不要なコードが字句解析器に渡されることとなり、これは(良いコンパイラでは)最もコストが高いフェーズとなってしまいます。

単純なルールに従いましょう。

本文に出てきた書籍

プログラム書法 第2版

プログラム書法 第2版

人月の神話【新装版】

人月の神話【新装版】

(翻訳)英語は私にとって15年にわたって悩みの種です

はじめに

Redisの開発者である@antirezが一昨日投稿したブログポストにとても共感したので翻訳しました。

世界一わかりやすい英文法の授業

世界一わかりやすい英文法の授業

僕が@antirezの文章を翻訳するのは今回が初めてではありません。RedisのドキュメントをまだRedisがバージョン2.0になったばかりの頃に日本語訳したのが最初でした。Redisドキュメント日本語化をしていた当時は翻訳しながら「ドキュメントが整っているなぁ」と感じたと同時に「独特の英語を使うなあ」という印象を受けました。その当時は彼が英語に苦労していた過去のことなど知らなかったので、こうして本エントリを読んで振り返ってみると、苦労しながら英語のドキュメントを整えた彼の労力に本当に頭が下がります。

感じることはたくさんありますが、まずは彼のエントリを読んでみてください。

英語は私にとって15年にわたって悩みの種です

ポール・グレアムがニュースサイトやソフトウェア開発者が注目している彼のブログの中で、IT関連職従事者に必要とされる英語という言語について、非常に重要な問題提起をしました。(追記:こちらも id:yomoyomo の日本語訳があります! c.f. 創業者の訛り) このエントリは「外国訛り」について触れたことやそもそもインターネット上には大げさに反応したがる人がたくさんいることから大いに賛否両論でましたが、その点は先ほど言った問題提起の中では面白くない議題なので、ここでは省略します。 重要な点は、通常誰も「英語問題」について話さない、というところです。そして私はいつもこの点で孤独に感じるのです。まるで私しか気にしていない問題のように感じるのです。なので、このブログポストで、私が英語を使ってきた中で経験してきたことを共有したいと思います。

話せば長くなります

私とsullivanミラノにあった私の自宅で酔っ払いながら、当時取り組んでいた新しい攻撃方法を編み出そうと頑張っていたことを思い出します。あれは1998年のことで、BUGTRAQの参加者なら見ればわかると思いますが、残念な結果に終わりました:http://seclists.org/bugtraq/1998/Dec/79

ここで2行目の "Instead all others" という部分に注目してください。私はいまだに英語が得意ではありませんが、15年以上かけて上達してきましたし、sullivanにいたっては今やアメリカとイギリスの大学で教鞭をとっているぐらいですから彼は相当流暢であることでしょう。(ネタバレ注意:私はまだ流暢ではありません) さて、それはおいといて問題は、私達はTCP/IPの新しい攻撃方法を紹介しようとしていたのに、二人ともそれを英語では全然うまく書けなかったのです。 1998年の当時、私はすでに英語でうまくコミュニケーションが出来ない、英語で書かれた技術文書は労力をつぎ込まないと読めないという事実から、ものすごく制限を受けているように感じていました。 そのことから、私の頭は単純に英語を読むという作業にその50%を使い、実際に読んでいるものを理解するための力は50%以下しか残されていませんでした。

しかしながら、どこかしら、いつも英語はいいものであるということを受け入れていました。人にはいつも技術的な話題では英語を訳さなくて済むようにするアドバイスしています。その理由は、ドキュメントやソースコードのコメントに共通言語を持つことは本当に良いことだと信じていて、実際に英語で書かれた技術文書を理解するために必要なスキルを得るのは多くの人にとって単純な努力だからです。

こうして、1998年から、私は少しずつ英語を勉強して、イタリア語の場合と比較しても変わらないほどにすんなりと英語を読めるようになりました。 それどころか、イタリア語でものを書くのと変わらない速さで英語をかけるようにもなりました。書く能力に関しては極小点にあるとしても、あなたがこのブログを読んでいるという事からも、ちゃんと書けていることは証明されています。基本的に私は砕けた簡単な英語をとても速く書くことで学びました。この方法は普通の場合、プログラミングの領域では自分の考えを表現するのには十分ですが、一般的な話題について書くには不十分です。 たとえば、私は台所で見つけたものについて何か書こうと思った時に必要な単語がほとんどわかりません。あるいは、複文、仮定法などを含んだ文を書くために必要な文法も知りません。 今や私が気になっている話題では容易にコミュニケーションがとれますし、その話題に関しては多かれ少なかれ私が書いたものはみんなが理解できるので、英語を上達させなければ、というプレッシャーは少しずつ減ってきています。。。しかしながら、最近これは私が英語について感じる問題の中では小さなものだとわかったのです。

ヨーロッパ英語、あの面白い言語

なんとかして、自分の用途には快適に英語を読み書きできるようになった一方で、最近まで英語圏の国で実際にコミュニケーションをしたことがほとんどありませんでした。 それまでは、英語をいつも(イギリス以外の)他のヨーロッパの人、例えばフランス、ドイツ、スペインといった国の人々との会話に使ってきました。 いまや、こうした国々で話される英語が英語学校の授業で話される英語となっています。音声学的に言って、この英語はアメリカ英語やイギリス英語とはほとんど無関係です。 これを「BBC英語」と呼ぶ人もいますが、実際は違います。イギリス英語の文法を使っている、音声学的に非常に単純化された英語です。

その 英語によって、実際に世界中の人々が容易にコミュニケーションを図れるようになりました。基本的な文法は習得する上で容易で、数ヶ月訓練すれば話せるようになります。単語の発音はヨーロッパ内のイギリス以外の国々ではほぼ一緒です。素晴らしく便利です。

唯一の問題があって、この英語は実際の英語圏の国、イギリス、アメリカ、カナダといった英語が母国語の国とは無関係だ、ということです。

結局英語は崩れつつある

ここであなたに秘密があります。英語と世界という文脈において誰も語らない秘密です。それは「英語は音声学上、砕けた言語だ」ということです。 イタリアには長い歴史がありますが、政権が統一されたのはつい最近のことです。異なる地域で異なる方言を使っていて、皆それぞれに非常に強い訛りがありました。 1950年より前に、「TV用言語統一」が起きた時に、まだ皆それぞれ自分たちの 方言 を使っていて、イタリア語はほんの少数の人間だけしか習得していない状態でした。 私の家族がよく使っているシチリア語もイタリア語が現れる何世紀も前から存在しています。(http://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%81%E3%83%AA%E3%82%A2%E8%AA%9E

それでもなお、面白いのが、ある地域の人が他の地域の人のイタリア語、たとえばスイスの人のイタリア語でさえも理解するのは全く問題がないというところです。 イタリア語は音声学的に地球上で最も単純かつ十分な冗長性を備えた言語の一つです。事実、イタリア語は情報エントロピーが低く、単語は通常子音と母音が程よく混ざっています。 単語を発音するときに特別なルールはなく、すべての文字の発音を知っていて、いくつかある「gl<母音>」「sc<母音>」という特別な組み合わせさえ覚えておけば、99.9%の単語を初見でも正しく発音することができます。

異なる英語を話す国々から来た人々がコミュニケーションをするときに問題があるという事実が、英語が音声学的にいかにおかしいかということを示す大きなヒントとなっています。 たとえば、私や他の非ネイティブの英語話者からすると、イギリス人が口からどんなクソを垂れてるのか、さっぱり全く全然聞き取れません。北米の英語のほうがよっぽど簡単です。

英語のこの「特徴」があるので、私の訛りではなく人が私に話していることを理解する力が問題となります。これは愚見で、前者は私がもっと努力すれば直せる単純なものです。 私見ではありますが、ポール・グレアムが「訛り」について触れたのは、この点においてイギリス人やアメリカ人の良くない姿勢です。言わせてもらいますが、あなた方は私達の話していることを理解していない、私達もあなた方が話していることを理解していない、そして一度露骨に理解しようとする線を制限してしまえば、落ち着いて会話を楽しもうとする人なんてほとんどいなくなってしまいますよ。 私はイギリス人の英語を理解できないとは言いましたが、すぐに復唱するくらいのことはしますよ。

文章だけで英語を学ぶのは本当に厳しい

私の意見では、私が英語学習に時間がかかった理由のひとつは、英語を全く聴き取りをせずに読み取りの練習を始めてしまったことです。 頭の中では、大量の英単語が綴りと実際にはありもしないおかしな発音とが結びついてしまっています。 私からのアドバイスとしては、もしいま英語を勉強しているのであれば、すぐに聴き取りも始めて下さい。

Mac OS Xに付属している "say" コマンドが良い助けになります。 "say" コマンドはたいていの英単語をきちんと発音してくれます。 発音を学ばずに英語を勉強しては いけません

内向的か外向的か

英語に関する経験として最も衝撃を受けたことの一つに、英語を習得していないことがどれほど人を内向的にさせるかという点がありました。 私はイタリアという、殆どの人が外交的な国においても外交的で、シチリアというさらに外交的な土地においても外向的で、外向的という要素で構成された家族内においても外向的でした。 私は思うにいわゆる目立ちたがり屋なのです(本当はそうは思いたくないのですが、非常に外交的です)。 そして、私が英語で話さなければならなくなった途端に、コミュニケーション障壁から外交性はまったく消え去り、その会議に参加したことや、誰かに紹介されることを後悔していました。あれは悪夢でした。

もう手遅れだ、英語を学ぼう

私の意見では、英語は文法が簡単なだけで、共通言語として選択するには間違っています。しかしながら現実は、すでに共通言語としての地位を獲得していて、もうその座を置き換える時間はなく、多くの努力が必要になったとしても英語をうまく話せるようになることを考えるほうがいいでしょう。 これは私がいま行っていることで、さらに改善しようとしている部分でもあります。

私が本当に英語を上達させなければならないと感じた他の理由は、10年後には私は職業としてコードを書くことはおそらくなくなって、選択肢としてIT系の管理職になる、もしくはコードを書くことを期待されていない大きなプロジェクトのリーダーとなる、のどちらかだからです。 開発者として英語が必要だと感じるとすれば、典型的なIT企業の他の部署に移って、さらには多くのプログラマをマネージメントする立場になっていくにしたがってそう思うようになるでしょう。

一方で、ネイティブの英語話者は、多くの人が英語という習得が大変な言語を本当に頑張って勉強しているということをきちんと理解すべきです。英語の勉強は趣味じゃないんです。英語を修得することは多くの人々がコミュニケーションを円滑にするために行なっている多大な尽力なんです。数週間英語を使わなくなっただけで、英語が言うに及ばないほどすぐに下手になってしまうんですから。

いつかは異なる訛りが1つの理解しやすい標準語にまとまって、それが英語話者の共通語となることを願っています。 (翻訳ここまで)

おわりに

ヨーロッパの人でも英語の習得に苦労しているという実体験を赤裸々に公開してもらえると、英語の習得に苦労している自分も励みになります。

僕もアメリカでパスポートから何から盗まれてしまっても自力で帰国するくらいは英語を使えるようになりましたが、それでもネイティブ英語話者の同僚とのコミュニケーションには苦労しています。それは英語だけではない文化の共有ができていなかったりだとか、そういう部分です。 これは英語という言語自体の習得だけではどうしようもない部分でしょう。ただ、ことITの世界の話題に関していえば、誰もが共通の文化をもってコミュニケーションが出来るわけで、その習得に労力を割くことは僕も大いに意味があると実感しています。こうやって彼のエントリをそのまま読めるわけですし。

日本には本当に優秀な技術者がたくさんいるにもかかわらず、英語というただ1点だけで損をしている事例を数多く見ているので、1人でも多くの技術者が1文字でも多くその技術を世界の人々に発信出来る日が早く来ればいいなと願っています。

追記

  • 2013.09.05 00:34 : +Jun Mukaiのコメントをもとに訂正
  • 2013.09.05 10:00 : id:yomoyomoポール・グレアムの原文「創業者の訛り」をリンクとして追加

原著者の許可

「Java開発者ための関数プログラミング」が出版されました

みなさま、ご無沙汰しております。1ヶ月以上ぶりのブログ更新となりました。Python界の情弱こと山口です。
この度、拙訳の「Java開発者のための関数プログラミング」という書籍がオライリー・ジャパンより電子書籍(ePub)で出版される運びとなりました。原著者はProgramming Scala(日本語版「プログラミングScala」)のDean Wampler氏。

<br />
Java開発者のための関数プログラミング
Dean Wampler 著、山口能迪、株式会社トップスタジオ 訳
フォーマット ePub

本書は、無理をしてJava関数プログラミングで実装してがんばろう、という本ではありません。一部そういうところもあるかもしれませんが、そういう方向は目指していません。あくまで関数型と呼ばれるものにはどういう概念がでてくるのか、それをJavaで関数型「っぽく」書いたらどうなるか、それにはどういう利点があるのか、なんかを概要レベルで説明した本です。
原著のページ数を見て分かる通り、非常に薄い本です。原著はたった90ページしかありません。なので既に関数型言語に触れている人には物足りないとおもいますが、JavaもしくはOOPしか触っていなくて、関数型言語を始める気力はないけど、なんとなく知りたい、という人にはいいのでは?と思っています。また参考文献も結構多いので、これをきっかけに本格的に関数型言語を使い始める方が増えるといいなあという期待もあります。*1ただこの本の内容は試みとしての要素が強い面もあるので*2、実際関数型でやるならば、本書にもあるように、はじめからそのように設計された言語を使われることをおすすめします。

本書の出版にあたっては、技術的な観点ふくめ、レビューをゆろよろ( @yuroyoro )さん、篠原さん( @itawasa )さん、 古瀬さん( @camlspotter )さんにして頂きました。Java特有の言い回しなどに関してはゆろよろさんと篠原さんのコメントが大変参考になりました。また、関数プログラミングに関連する内容に関しては古瀬さんより多大なる助言をいただきました。特に本書の肝である関数プログラミングに関する部分は、原著の解説がおかしい思われる部分もあり *3、その点をご指摘いただけたことに大変感謝しております。
この誤っている部分に関しては原著者であるDean Wampler氏に伝えます。著者の了解が得られていないため、現時点で日本語訳に反映出来なかったことが残念ですが、このブログも含め、どうおかしいと思うのか、本来はどうあるべきであるか、などをお伝えしたいと思っています。

また私の拙い日本語を何度もチェックいただき丁寧にご指摘くださった勝野さん、トップスタジオやオライリー・ジャパンの方々の助けがなければ本書は脱稿することができませんでした。特に遅筆な私に発破を掛けて下さった、トップスタジオの雨宮さんにはご迷惑おかけしました。そしてオライリー・ジャパンの瀧澤さんには日本語版の出版の企画をご提案いただいた事から始まり、公私に渡ってご支援下さったことに感謝しております。
日本語訳は、当初直訳すぎるとのご指摘を頂いた箇所もありましたが、なるべく一語、一文の持つ意味を崩さないようにすることを心がけました。

翻訳するきっかけは、原著の第1章を読んで「これは面白そうだ」と思ったことだけでした。ほぼ勢いです。そんなきっかけから初めて商業書籍の翻訳をしたわけですが、自分の日本語力の低さに愕然とするばかりで、いま終えてみると市場に出ている翻訳本を見る目が変わりました。皆さん、本当に苦労されたんだろうなあと。多くの方々にサポート頂いて、なんとか出版することができたことに、改めて感謝致します。

個人的には今回の出版によってオライリーの書籍にBeepLoudとDJという単語を載せたことが最大の仕事だと思っています。どうぞお手に取って下さい。

追記 (2012.06.23 10:40)

上にもリンク貼りましたがこの書籍用のG+ページ作りました。「感想用スレッド」というのがあるので、そちらに書いていただいてもいいですし、TwitterGoogle+でハッシュタグ #fp4jd でコメント頂ければ反映していきます。

追記 (2012.06.21 10:39)

著作権法の関係上、すでに1年前に原著が出てしまっている状況なので今回の日本語訳出版の際に反映出来なかったことが残念ですが」という表現において、「1年前に原著が出ていること」と「著作権法」の間の相関がないため、今回の状況が起きている後者に関する記述を明確にしました。ご指摘いただきありがとうございます!

*1:自分自身がまだ初心者でもありますので

*2:実際Java 8に関して言及している箇所もある

*3:具体的には遅延評価と末尾呼び出し最適化に関する一部

なぜ次に学ぶ言語は関数型であるべきか

はじめに

こんにちは、Python界の情弱です。ちょっと前にOCaml系のエントリを色々と眺めていたらYaron Minsky氏のエントリを見つけたので翻訳してみました。

Yaron Minsky氏はJane Streetで第一線で活躍されるエンジニアで、Jane Streetの技術ページをはじめ多くの場所でOCamlに関しての知見を語ってくださっています。

本エントリはJohn Hughesの名エントリ「なぜ関数プログラミングは重要か」を受けてACM Queueに寄稿されたものの日本語訳です。

Why the next language you learn should be functional

YARON MINSKY, JANE STREET

エレガントな実装は関数である。メソッドでも、クラスでも、フレームワークでもなく、ただの関数である。 - John Carmack(訳注 : John Carmack氏はアメリカのゲームプログラマで、FPSの生みの親と言われています。*1

関数型プログラミングは卓越した歴史を持つ、昔からある発想です。Alonzo Churchのラムダ計算に刺激されたLispは計算機の歴史の夜明けから発展してきた最初の関数型言語の一つです。OCamlHaskellのような関数型の静的型付け言語は新しいですが、そのルーツはかなり昔にさかのぼります。--その先祖であるMLは、70年代初頭にRobin Milnerが行った先駆的なLCF(Logic for Computable Functions)定理証明器に関する研究までさかのぼります。

また関数型プログラミングは多大なる影響を与え続けて来ました。言語設計における多くの基礎的なレベルでの先駆性、ガベージコレクションから型推論へのジェネリクスに至るまで、が関数型の世界から飛び出して、他の言語で実装されるよりも何十年も前に関数型の世界では当たり前のものになっていました。
それでもなお関数型言語は本当の意味でまだ主流にはなったことはありません。SymbolicsやLispマシンが流行った時代にはおそらくかなり良い線までは行っていましたが、それもいまや昔の話です。ここ数年の関数型プログラミングの復権にも関わらず、その技術自体は実際に使われると言うよりも議論の対象にとどまっています。

こうやってみると関数型言語にはなにか必要な物が足りないと結論づけたくなります。関数型言語は特定のアプリケーションでは非常に理にかなっていて、他の言語にも取り込まれるような有用な概念も持っています。しかし、命令形言語やオブジェクト指向言語は単純にソフトウェアエンジニアリングにおける圧倒的多数のタスクに向いていると言いたくなります。
魅力的に見えますが、この結論は間違っています。私は10年近くプロダクション環境においてOCamlを使ってきました。そして時が経つにつれ、私は関数型言語、特にOCamlHaskellのような強い型付けの言語は完璧な汎用目的のプログラミングツールであると確信しました。--現存する他のどんな主流言語と比較してもです。これらの関数型言語は幅広くもあります。小さいスクリプトタスクから大規模高パフォーマンスアプリケーションに至るまで適しています。すべての仕事に向いているとは言いませんが、かなり近いところまでいっています。

OCamlへの移行

私のOCamlによるプログラミングの経験のほとんどはJane Streetでの仕事上におけるものです。Jane Streetは2000年に設立された金融会社です。9年前、Jane Streetの誰一人としてOCamlの名前を聞いたことがありませんでした。いまや、JaneStreetはOCamlの最大の企業ユーザです。どれほどかといえば、約200万行のOCamlコードがあり、日常業務でOCamlを使っている従業員が(全部で)65人います。おそらく、なにがOCamlをそれほどまでに効果的なツールにしているかを説明するには、どのように、そして何故OCamlへの転換が起きたかを説明するのが一番だと思います。それを理解するために、まずJane Streetで何が行われているかを理解する必要があります。
Jane Streetの本業は世界の電子化された金融市場に流動性を与えることです。それは本質的には仲介人をするということです。多くの有価証券の多くの取引に発注をし続けます。発注の1つ1つがある証券をある価格で売るか買うかの意志となっていて、全体でJane Streetが提供する市場の広告となっているわけです。これらの発注を通して、企業は売る必要がある人から買い、買う必要がある人に売り、売買の価格の差で利益を得るわけです。常に同じような事をしようとしているプレーヤーとの競争になります。

電子的な流量を供給することは、技術的にとてもやり甲斐があります。それは必要とされる計算リソース(莫大な量のデータが消費され、解析され、その結果がリアルタイムに返されます)だけでなく、エンタープライズの複雑さ、という点でもです。--取引は複数の取引所、強制力のある団体、セキュリティ階層、タイムゾーンをまたがります。結果として出来てしまう複雑さを管理することは、ソフトウェアに大幅な投資を必要とする、とても手ごわいタスクです。

これら全てに関わる技術はリスクをもたらします。どの程度かといえば、取引会社が自爆する方法では、タイトループで残念な選択を何度も何度もし続けるトレードソフトをデプロイするのが最も速い、といえば分かるでしょうか。これらの技術的リスクに対してのJane Streetの反応として、容易に理解出来るソフトウェア、言い換えれば可読性が高いソフトウェアを作る事に非常に強く重点を置く、というものがありました。

コードを読むというのは、OCamlの最初の一行を書き始める前に会社がとったリスクに対するアプローチの一つでした。初期の段階から、(創始者を含む)何人かのシニアトレーダーが核となる取引システムに追加されるコードを、それがプロダクションになる前に、一行一行読んでいました。これは莫大な時間の投資であり、技術リスクに対する高い懸念と反映したものでした。

私がJane Streetに入ったのはPh.Dを取った年でした。ポスドクをしている間はパートタイムで働いていました。Jane Streetでの仕事は統計的解析やトレーディング戦略の最適化を中心にしていて、OCamlは解析を行う上での主要ツールでした。なぜOCamlなのか。私は修士課程でOCamlを学び、この言語に恋に落ちました。そしてOCamlはこのような素早くプロトタイプを作るような仕事にとても適しています。効率が非常に高く、それでいてC, C++, Javaと比較して速くエラーを起こしにくいです。私はJane Streetに在籍する期間は短く、自分が書いているコードはすべて捨てられると確信していたので、他人が自分のコードを後で使えるかどうかという心配を全くすることなく自分の生産性を最大化する選択をしました。6ヶ月で80,000行のコードができ、私は間違っていたと気づきました。気がつけばJane Streetでフルタイムの職に就いていました。そしてすぐに研究グループを作るための採用を始めました。
このとき、会社はソフトウェア開発で新しい手法に方向転換しようとしていました。会社の業務を回していたシステムははじめの数年は主にVBAC#で書かれていました。実際コアとなる取引システム自体はとんでもない量の独自VBAが追加されたExcelスプレッドシートでした。これは立ち上げてぱっと走らせるには良かったのですが、持続できる手法ではないことは明らかでした。
2003年、Jane Streetはコア取引システムをJavaで書き換えました。ここで書き換えたコードは結局破棄されました。その理由の一部としては、コードの可読性が下がりすぎて、状態を判断するにも難しくなりすぎたということが挙げられます。--実際置き換えられようとしているVBAよりも難しくなってしまったのですから。この原因の大部分はJavaの冗長性にあります。しかし冗長性だけにはとどまりません。VBAのコードは単純明快で真っ直ぐなコードなためかなり読み進めやすいです。しかしJavaでコードを書くと、どういうわけかクラスが入れ子状態のものが出来上がって、ただどのコード片があるメソッドが呼ばれたときに実際に何が呼び出されているかを理解するだけのために人々が頭をかきむしるようなものとなってしまいます。継承を多く使うようなコードは特に考えるのが難しいです。その理由の一部は、継承は抽象化という境界の下に潜ってしまうからです。
2005年、研究グループの成功が励みとなって、Jane Streetは再度コア取引システムを書き換え始めました。今度はOCamlでした。最初のプロトタイプは3ヶ月で行われました。そして取引を始めるまで更に3ヶ月かかりました。それから社内でのOCamlの使用はただ拡大していきました。今日では、OCamlは社内での問題解決のあらゆる部分に使われています。会計からシステム管理まで、あらゆるところで。そしてそういった努力は成長し続けています。ここ数年、会社の取引がOCamlの使用を拡大し、OCamlのトレーニングが今や新しい取引での雇用者のカリキュラムの標準となっています。結果として、OCamlへの移行は大成功で、他のどのやり方で得られたものよりもずっと強い技術を得ることが出来たのです。

なぜOCamlなのか?

とてもうまくいく言語というのに必要なものはなんでしょうか?ここに、私がOCamlのキーとなる強みとして気づいたところを短くまとめてみました。

  • 簡潔さ: 私たちが研究の中でOCamlを使ってきた経験からすると、OCamlを使えば、JavaC#のような言語で作るよりも、小さくて、簡潔で、理解しやすいシステムを作ることができると確信しています。可読性を重要視する組織であれば、これは大きな勝利です。
  • バグの発見: OCaml未経験だったプログラマは型システムがバグを捕まえる程度にしばしば驚かされます。その時に得られる印象といったら、なんとか型チェッカーからコードに対して正しいという判定をもらったら、もうバグは残ってないと思うくらいです。もちろん実際はそうは行きません。OCamlの型システムは多くのバグに対して役に立ちません。しかしながら、テストではかなり見つけにくいようなバグも含め、型システムが効果的なバグというのは驚くほどたくさんあります。
  • パフォーマンス: OCamlのパフォーマンスはJavaと同等あるいはそれよりも上で、CやC++に手の届く距離にいるとわかりました。高品質なコードジェネレータがあることに加えて、OCamlにはインクリメンタルGC(ガベージコレクタ)があります。これはGCが小さな仕事の塊を1度に片付けるようにチューニングすることができて、電子取引のようなソフトリアルタイムアプリケーションにより適した形にできるということです。
  • 純粋、たいていは: 関数型プログラマはよく話に挙げるにも関わらず、mutable状態はプログラミングの基本的な部分であり、それを取り去ることはできないし、取り去るべきではありません。ネットワークパケットを送ったり、ディスクに書き込んだりするというのはmutableの例です。immutableに完全に取り組むということは、本物を決して作らないということです。しかしながらmutable状態はそれ自体がコストとなります。破壊的な操作が一切ないコードは一般的に読み解くのが簡単で、コード内でのやり取りや依存関係を明確で、管理が楽です。そういう意味でOCamlはいいバランスを保っています。破壊的操作は容易にしていますが、immutableなデータ構造を既定としています。上手く書かれたOCamlシステムはほとんどがmutable状態を持っていますが、その状態は注意深く制限されています。

おそらく最も簡単にこれらの利点を具体的にデモするには、簡潔性をお見せするのがいいでしょう。簡潔性において大事なことは曖昧さがないことです。言い換えればすなわち、短いコードは読みやすく、書きやすく、維持管理しやすいということです。もちろん限界はあります。すべての関数名を1文字にするなんていうのはだめです。とはいえ短いということは重要です。OCamlにはコードを小さくする仕組みがたくさんあります。
OCamlの利点の一つとして挙げられるのは型推論です。これによって型宣言をする必要性を取り除きます。これによってPythonRubyのような動的型付け言語で書く時とほぼ同じくらい短くコードを書くことができます。同時に、静的型付けのパフォーマンスや正確さを享受することもできます。次のようなタプルを変形するOCamlの関数mapを考えましょう。

let map f (x,y,z) = 
   (f x, f y, f z) 

ここでmapは2つの引数を持つ関数と定義されています。関数fと3要素タプル (x,y,z) です。f xは関数fをxに適用するという構文です。ではC# 4.0ではどうなるか見てみましょう。C#のコードは機能的には等価ですが、見た目は雑然としていて、実際の構造が構文のノイズによって不明瞭になっています。

Tuple<U,U,U> Map<T,U>(Func  <T,U> f, Tuple<T,T,T> t) 
{ 
   return new Tuple<U,U,U>(f(t.item1),  f(t.item2), f(t.item3)); 
} 

簡潔性の他の要因としてはOCamlにある型の表現の表記にあります。この表記の中心にあるのは代数データ型です。代数データ型とは積または和で新しい型を作ることが出来るシステム内で得られるものです。

直積型は2つの中ではよく知られている方です。タプル、レコード、構造体、オブジェクトはすべて直積型の例です。直積型は異なる型の複数の値を1つの値に結合します。これらは数学的に成分のデカルト積に対応するので直積型と呼ばれています。
直和型は成分の直和に対応して、複数の可能性を表すのに使われます。直和型が使われるのは複数のもの(aとbとc)を同時に表現したい時に使われます。直和型は(不格好ではあるけど)Javaでサブクラスを使うといった具合に、オブジェクト指向でモデル化出来ます。またCでは共用体として登場します。しかしほとんどの言語で直和型を安全に扱う上での型システムにおけるサポートは、驚くほど弱いのです。

次のコードは動作する代数データ型の例です。このコードは基礎述部のセットの上に成り立つBoolean式を表す型と、それらの式を評価する関数を定義しています。コードは基礎述部のセット上では一般的なので、これらの式の主部は整数の違いから、コンパイラフラグの設定まで、何にでも使えます。

  • OCamlでの表現型と評価器
type 'a expr = | True 
               | False 
               | And  of  'a expr * 'a  expr 
               | Or   of  'a expr * 'a  expr 
               | Not  of  'a expr 
               | Base of  'a  
 
let  rec eval eval_base expr  = 
   let  eval' x = eval eval_base x in 
   match expr with 
   | True  -> true 
   | False -> false 
   | Base base  -> eval_base base 
   | And  (x,y) -> eval' x && eval' y  
   | Or  (x,y)  -> eval' x || eval' y 
   | Not  x     -> not (eval' x) 

直和型の式は異なる宣言部を分けているパイプで示されています。これらの宣言部の内、たとえばTrueやFalseは単一タグで、JavaやCの列挙型の要素と実質的には変わりません。他のもの、たとえばAndやNotは結びついているデータがあって、そのデータは場合によって変わります。この型はAndやOrの部分がタプルを含んでいるので、実際直和型と直積型の両方を含んでいます。積と和の重構造の組み合わせで成り立つ型はOCamlではよくあるもので、強力なイディオムです。
注目すべき構文としては型変数 'a があります。型変数はどんな型でもインスタンス化でき、これによって基本述部のセットの上でコードをジェネリックにできるのです。これはジェネリック型のJavaC#での扱われ方と似ています。すなわち、Javaの <A>List はOCamlでは 'a list と表現されます。
関数 eval は2つの引数を取ります。評価される式 expr と基本述部を評価する関数 eval_base です。コードはevalはあらゆる型の基本述部上の式に対して使うことが出来るという意味でジェネリックですが、 eval_base は基本述部の審議を評価するために必ず与えられなければいけません。関数 eval' は引数 eval_base で eval するための再帰呼び出しを簡単にするために定義されました。最終的にmatch文は考えられる式構造の条件解析と基本述部を評価するときの eval_base の呼び出し、そしてそれ以外の場合にデータ型の構造に対して再帰として動作するために使われます。
次のコードは同じコードをJavaで書いた場合です。冗長なのがまず目立ちます。Andのような1つの条件を追加するときに、OCamlでは2行(しかも短い)なのに、Javaでは8行です。--そしてこのJavaのコードは動作するものとしてはかなり最小にしています。もしクラス定義まで落としこまれていないこの表現型に対して、ほかのアルゴリズムを作らせようとした場合、ビジターパターンを使いたくなるでしょう。その場合行数は凄まじく膨らみます。

  • Javaでの表現型と評価器
public  abstract class Expr<T> { 
 
  public interface Evaluator<T> { boolean evaluate(T value); } 
  public abstract boolean eval(Evaluator<T> evaluator); 
 
  public class True<T> extends Expr<T> { 
    public boolean eval(Evaluator<T> evaluator) { return true; } 
  } 
  public class False<T> extends Expr<T> { 
    public boolean eval(Evaluator<T> evaluator) { return false; } 
  } 
  public class Base<T> extends Expr<T> { 
    public final T value; 
    public Base(T value) { this.value = value; } 
    public boolean eval(Evaluator<T> evaluator) 
    { return evaluator.evaluate(value); } 
  } 
  public class And<T> extends Expr<T> { 
    public final Expr<T> expr1; 
    public final Expr<T> expr2; 
    public And(Expr<T> expr1, Expr<T> expr2)  { 
      this.expr1 = expr1; 
      this.expr2 = expr2; 
     } 
    public boolean eval(Evaluator<T> evaluator) { 
      return expr1.eval(evaluator) && expr2.eval(evaluator); 
     } 
  } 
  public class Or<T> extends Expr<T> { 
    public final Expr<T> expr1; 
    public final Expr<T> expr2; 
    public Or(Expr<T> expr1, Expr<T> expr2)  { 
      this.expr1 = expr1; 
      this.expr2 = expr2; 
     } 
    public boolean eval(Evaluator<T> evaluator) { 
      return expr1.eval(evaluator) || expr2.eval(evaluator); 
     } 
  } 
  public class Not<T> extends Expr<T> { 
    public final Expr<T> expr; 
    public Not(Expr<T> expr) { this.expr = expr; } 
    public boolean eval(Evaluator<T> evaluator) 
     { return !expr.eval(evaluator); } 
  } 
} 

言語の他の側面でもっと説明を必要とする部分としては型システムがバグを捕まえる能力があります。OCamlや関連した言語(あるいはそれにに詳しい人)をよく知らない人はしばしば型システムの力を誤って低く見積もってしまいます。型システムがすることは、変数に正しく渡していることを確実にすることだけだ(たとえば、floatを想定しているところにfloatを入れる)と結論付けるのは簡単です。
しかしそういうだけ以上のものがあります。純粋に型システムを使うだけでも恐ろしいほどバグを捕まえます。次のリスト内の重複を削除するPythonコードを考えてみましょう。

# Removes sequential duplicates, e.g., 
# destutter([1,1,4,3,3,2])  = [1,4,3,2] 
 
def destutter(list): 
    l = [] 
    for i in range(len(list)): 
        if list[i] != list[i+1]: 
             l.append(list[i]) 
    return l 

このコードはとても直線的ですが、バグがあります。このコードはリストの末尾を上手く扱えません。これを直してみましょう:

def destutter(list): 
    l = [] 
    for i in range(len(list)): 
        if i + 1 >= len(list) or list[i] != list[i+1]: 
             l.append(list[i]) 
    return l

ここでOCamlで同じような関数を書いたら同じようなバグが起きるか見てみましょう:

let rec destutter l = 
  match l with 
  | []             -> [] 
  | x :: y :: rest -> 
    if  x = y then destutter (y :: rest) 
    else  x :: destutter (y :: rest) 

このコードはリストの要素にアクセスするためにOCamlのパターンマッチ構文を使っています。ここで :: はリストのコンストラクタで、 [] は空のリストを表しています。したがって、 [] の場合は空のリストに対応して、 x::y::rest の場合は少なくとも2つの要素を持つリストに対応します。変数 rest は残りのリスト(空のこともある)を参照しています。
Pythonでの例のようにこのコードが失敗して、リストの最後まで来て要素が1つしか残っていない場合に何が起きるかを考えてみましょう。この場合は、実行時ではなくコンパイル時に問題を検出できます。コンパイラは次のようなエラーを表示します:

File "destutter.ml", line 2, characters 2-125: 
Warning 8: this pattern-matching is not exhaustive. 
Here is an example of a value that is not matched: 
_::[] 

忘れている条件は _::[] で、リストに1つしか要素がない場合です。
コードを直し(そしてコンパイラに怒られないようにし)てあげるには、忘れている条件を追加してあげればいいのです:

let rec destutter l = 
  match l with 
  | []                -> [] 
  | x :: []          -> x :: [] 
  | x :: y :: rest -> 
      if x = y then destutter (y :: rest) 
      else x :: destutter (y :: rest) 

ここで扱ったエラーはテストをすれば簡単に見つかるような些細なものです。しかし、型システムはテストがなかなかできないようなエラーも同様に見つけ出します。たとえばテストでは見落としやすい非常に稀にしか現れない問題や、モックアップや実例を作るのが非常に難しい複雑なシステムに現れる問題などです。
OCamlはインストールしてすぐの状態ですでにバグを捕まえることに非常に長けていますが、型を慎重に設計すればさらにその能力を発揮することができます。ネットワークコネクションの状態を表している次のような型を例として考えてみて下さい:

type connection_state = 
| Connecting 
| Connected 
| Disconnected 
 
type connection_info = { 
  state:                   connection_state; 
  server:                  inet_addr; 
  last_ping_time:          time option; 
  last_ping_id:            int    option; 
  session_id:              string option; 
  when_initiated:          time option; 
  when_disconnected: time  option; 
} 

connection_state 型はコネクションが成りうる3つの状態に名前をつけただけの単純な列挙型です。connection_infoはコネクションの異なる側面を表現するフィールドをいくつも含んだレコード型です。型の最後に option と付いているフィールドは本質的にはヌルを許可するフィールドです。(デフォルトで、OCamlの値はヌルではないことが保証されています)それ以外にはこのコードはJavaC#で書いた場合と何も違いはありません。
次にレコードの個々のフィールド同士の関係を記述します:

  • server はコネクションの相手方のサーバを識別するものです。
  • last_ping_time と last_ping_id は keep-alive プロトコルの一部として使われます。これらのフィールドの両方が存在するか、両方共存在しないかのどちらかであることに気をつけてください。またこれらは state が isConnected の時にのみ存在します。
  • session_id はコネクションが確立される度に新しく選ばれる一意識別子です。これも state が isConnected の時のみ存在します。
  • when_initiated はいつコネクションを確立しようとしたかを記録するためにあります。これはコネクションの試行をいつ破棄するかを決定するために使われます。これは state が isConnecting の時にのみ存在すべきです。
  • when_disconnected はいつコネクションが Disconnected の状態になったかを記録します。そしてその状態のときにのみ存在すべきです。

ご覧のとおり、多くの不変条件が異なるフィールドに結び付けられています。これらの不変条件を管理することが本当の仕事です。あとで訳が分からなくならないように、慎重に文書化する必要があります。これらの不変条件を検証するためにテストを書く必要があります。そしてコードが進化するに連れて不変条件を破ってしまわないように警戒を続けなければいけません。しかしそれも上手くやれます。次の様に書き換えるのです:

type connecting   = { when_initiated:  time; } 
type connected    = { last_ping  : (time * int) option; 
                                 session_id: string; } 
type disconnected = { when_disconnected: time;  } 
 
type connection_state = 
| Connecting   of connecting 
| Connected    of connected 
| Disconnected of disconnected 
 
type connection_info = { 
   state:  connection_state; 
   server: inet_addr; 
} 

これで直積型と直和型の組み合わせとなって、より正確にコネクションの取りうる状態を表現できるようになりました。特に、3つの状態に対して異なるレコード型を用意しています。それぞれはその状態に対してのみ関係がある情報を含んでいます。常に関係がある情報(この場合はserverだけ)は最上位のレコードに移されています。また、 last_ping_time と last_ping_id は両方共存在するか、あるいは両方共存在しないかを last_ping 飲みを用意することで明示的にしました。これはオプショナルなペアになっています。

これらをすべて行うことで、多くの必要な不変条件を型の中に埋め込みました。いまや不変条件は型の一部なのです。コンパイラはこれらの不変条件を破るコードを見つけ、拒否することができます。これは手作業で行う仕事を減らし、手で管理するよりも信頼できます。

この例は不変条件を実装するために代数データ型を使いました。しかしOCamlは同じ事をするのに別の道具も用意しています。OCamlのモジュールシステムはその一例です。不変条件をモジュールのインターフェースとして定義できます。大抵のオブジェクト指向言語とは違って、OCamlでは複雑に組み合わさった不変条件を複数の異なる型で表現することができます。より一般的に言えば、OCamlのモジュールはコードを小さく、理解しやすいパーツに落としこむ強力な道具であり、これらのパーツ同士のやり取りはプログラマが明示的に制御できるようになるのです。

型システムがバグを捕まえる能力は単独の小さなプロジェクトでも価値がありますが、真価を発揮するのは複数の開発者が一緒に生存期間が長いコードベースを共有するような協調環境においてです。バグを見つけるだけでなく、型シグネチャは正しいことが保証されたドキュメントとして驚くほど価値ある役割を担っています。進化するコードベースの中では型システムで強化された不変条件は規約によって管理されているものよりもずっと耐久性が高いという利点があります。その結果他の開発者によって誤って壊されるということが少ないです。

制限

この文書はOCamlには欠点がない、と言おうとしているわけではありません。もちろん、マイナー言語であることに起因する問題はすべてあります。OCamlには豊富なライブラリ群を生み出す素晴らしいコミュニティがありますが、それらライブラリ群もPythonやCやJavaにあるものと比べると見劣りします。同様にIDE、プロファイラ、デバッガのような開発ツールもありますが、メインストリームの言語と比べると成熟度合いと機能が著しく不足しています。

OCamlの他の制限で並行性を挙げなければいけません。OCamlのランタイムには単一ランタイムロックがあります。これはつまり、1台のマシンにある複数コアの恩恵を預かるにはマルチプロセスを使わなければいけないということです。大抵の場合、これは我々の開発モデルに適応します。並行性のプログラミングモデルとして共有メモリのスレッドを使うよりもメッセージパッシングを好むので、この特徴は読みやすいコードにつながり、複数の物理マシンの間でスケールアウトしやすいコードとなります。広くOCaml界でこのようなマルチプロセスプログラミングをするツールが手に入ります。しかし、まだ未成熟なのです。

しかしOCamlの制限は全く根本的なものではありません。それは実装方法や言語の人気に関係するもので、言語そのものにおけるものではないのです。終わりになりますが、それが私が最も困ったことだと思ったことなのです。いまや私はOCamlの背景にあるコアなアイディアというのは非常に価値があると確信していて、それはOCamlそれ自身が、たとえどんな制限があろうとも、非常に効果的で強力な道具だという事実に裏打ちされています。しかし、これらのアイディアは依然としてメインストリームの外にあります。

おそらくこれは変化の境界にあるのだと思います。F#やScalaのような言語は、それぞれ.NetやJavaのエコシステムと統合することで、OCamlHaskellの背景にあるアイディアを大衆にもたらしています。おそらく10年後には、なぜこれらのアイディアは大衆に受け入れられなかったかなどと質問する必要はなくなるでしょう。しかし何も10年待つ必要はないのです。OCamlをあなたの道具箱に今追加すればいいのです。

原著者の許可

from: Yaron Minsky
to: Yoshifumi YAMAGUCHI
date: Thu, Nov 10, 2011 at 12:40 PM
subject: Re: Request for translation of your article

I'm really glad that you translated the article, but I don't know if I can personally approve it --- I think the ACM has some rights to the article now that it's been published in Queue, so if you want formal approval you'll have to ask them.

But I certainly have no objections. Thanks for doing it!

y


On Wed, Nov 9, 2011 at 9:40 PM, Yoshifumi YAMAGUCHI wrote:
> Hi Yaron,
>
> Nice to meet you.
> I'm Yoshi who is interested in OCaml and I've been affected with your article, "Why the next language you learn should be functional".
> http://queue.acm.org/detail.cfm?id=2038036
>
> Though it is retrospective approval, I'd like to receive it from you for translating the article > into Japanese to introduce your impressive article to Japanese engineers.
> http://d.hatena.ne.jp/ymotongpoo/20111105/1320506449
>
> Would you approve it?
>
> I sent this message to you company e-mail address, sorry for sudden and rude request.
> I consulted Jun Furuse for my translation and he said my interpretation is acceptable.
>
> Cheers,
> Yoshi
> ----------
> Yoshifumi YAMAGUCHI
> @ymotongpoo
> http://www.google.com/profiles/ymotongpoo

*1:id:camlspotter 指摘有難うございました