本書は、はじめてプログラミングを知ったときに楽しさを思い出させてくれるものです。紹介されているプログラミング言語は7つ。
- Ruby
- Io
- Prolog
- Scala
- Erlang
- Clojure
- Haskell
単なる言語紹介の本ではなく、言語を知る本です。
本書を読むことで、各言語の長所、短所、原理、思想を知ることができます。プログラマとして一皮向けたい人に、おすすめです。
禅の指導者は、数学ができるようになりたければラテン語を勉強せよと言うだろう。プログラミングでも同じだ。オブジェクト指向プログラミングの本質を深く理解するには、論理プログラミングや関数型プログラミング(FP)を勉強する必要がある。関数型プログラミングに上達したければ、アセンブラを勉強する必要がある。
プログラミングとは結局、理解することであり、理解できるかどうかはどれだけアイデアの引き出しがあるかにかかっている。したがって、新しい言語を直接体験することは、プログラミングが何たるかをより深く理解するために欠かせない。
おぼえがき
Ruby
Ruby は純粋なオブジェクト指向言語である。オブジェクト指向の設計哲学において重要な、実装ではなくインターフェースに合わせてコーディングを行うというのを、Ruby ではダックタイピングによって実現する。
Ruby には多くのシンタックスシュガーが用意されており、開発者の生産性を高める工夫が数多く用意されている。
参考
スプーン一杯の砂糖があるだけで、苦い薬も飲めるのよ。
Io
Io(イオ)はプロトタイプ言語であり、すべてのオブジェクトは別のオブジェクトのクローンである。
Io はオブジェクト指向言語で、シンタックスは単純にメッセージをチェーン接続したものになる。各メッセージはオブジェクトを返す。すべてのものは別のレシーバを返すメッセージである。
Io にはキーワードはない。ただし、キーワードのように振る舞う文字がいくつかある。
クラスとオブジェクトの両方を意識する必要はなく、もっぱらオブジェクトだけを扱えばよい。必要に応じてオブジェクトを複製する。これらのクローンはプロトタイプと呼ばれる。
プロトタイプベースの言語では、すべてのオブジェクトが既存のオブジェクトのクローンとなる。
参考
オブジェクト、タイプ、インスタンス
1 2 3 4 |
|
慣習上、Io ではタイプの先頭文字に大文字を使う。先頭が大文字のオブジェクトを Io はタイプとして認識する。
メソッド
Io ではメソッドは次のように定義する。
1
|
|
メソッドもオブジェクトなので、スロットに代入できる。
1
|
|
プロトタイプ・プログラミングのパラダイム
- すべてのモノはオブジェクトである。
- オブジェクトとのすべてのやり取りはメッセージを介して行う。
- クラスをインスタンス化するのではなく、他のオブジェクト(プロトタイプという)を複製する(クローンを作成する)。
- オブジェクトは自身のプロトタイプを記憶している。
- オブジェクトにはスロットがある。
- スロットにはオブジェクト(メソッドオブジェクトを含む)が格納される。
- メッセージはスロットが保持している値を返したり、スロットに格納されているメソッドを呼び出したりする。
- オブジェクトは、自分が応答できないメッセージを自分のプロトタイプに送信する。
コレクション
Io には List と Map の二つのコレクションが用意されている。
1 2 |
|
list は List プロトタイプを作成するメソッドである。Map を作成する方法はクローンしかない。
true, false, nil, シングルトン
true, false, nil はシングルトンとして定義されている。自分のクラスをシングルトンとして定義するには次のようにする。
1 2 |
|
メッセージ
Io ではほとんどすべてのものがメッセージになる。メッセージは sender(送信元、呼び出し元)、target(送信先、宛先、呼び出し先)、arguments(引数)のコンポーネントからなる。
call メソッドを使うと、任意のメッセージに関するメタ情報を参照できる。
1 2 3 4 5 6 7 8 9 10 |
|
objA の myMethod スロットは、メソッドの呼び出し元の情報を表示する call sender が定義されている。これを、objB の myMethod スロットが呼び出すことによって、sender は objB が参照されるため、objB のメタ情報が表示される。
(Io を Mac OS X にインストールしようとしたところ、make でエラーが出てしまってインストール出来なかったため、おぼえがきはここまで。。)
Prolog
ルールベース言語である Prolog を使えば、論理を表現したり質問をしたりできる。Prolog もデータベースを扱うが、論理ルールと関係から成り、データを表現する部分とデータに質問する部分で構成される。
Prolog は次の構成要素からなる。
- 事実
特定の世界についての基本的な表明
- ルール
その世界の事実に関する推論
- 質問
その世界に関する質問
Prolog では、答えに至る道筋をコーディングするのではなく、純粋な論理を使って知識をコーディングする。あとは Prolog がその知識を組み合わせて答えを見つけてくれる。我々プログラマは、知識ベースに論理を組み込んで、それに対して質問をするという形になる。
参考
アトムと変数
Prolog では小文字で始まる単語をアトムと呼び、固定値の定義になる。大文字かアンダースコアで始まる単語は変数である。
知識ベース(事実、ルール)
Prolog のプログラムの例
1 2 3 4 5 |
|
likes(.., ..) の部分が事実になり、 friend(.., ..) の部分がルールになる。\+ は否定なので、friend のルールは、X と Y が一致せず、X が Z を好きで、Y が Z を好きな場合となる。
地図の塗り分け問題の例
本書より抜粋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
隣接する州を同じ色にぬらないように色を決めるというコーディングが、たったこれだけでかけてしまう。
Prolog では、事実と推論でロジックを表現し、利用者に質問させる。手順を追った解法を作る必要はない。
Prolog の得意とする問題
Prolog では問題の解法を記述する必要はない。問題を記述するだけでよい。そして、問題を記述するための言語は純粋な論理だけだ。事実と推論から始めれば、あとは Prolog がやってくれる。Prolog のプログラムは高レベルの抽象化を実現する。この事例のスケジュール作成と行動パターンは、Prolog が得意とする問題だ。
再帰処理のメモリ不足
ルールをネストする場合、繰り返しか再帰を使う必要がある。宣言型言語である Prolog ではこの問題の場合、再帰を使う。
宣言型言語は、再帰によるスタック領域の消費にともなるメモリ不足への対応として、末尾再帰最適化という手法で解決していることが多い。Prolog は再帰部分がサブゴールの最後にある場合、最適化を行うため、メモリ不足に対処することができる。
リストとタプル
リストは、[1, 2, 3]、タプルは (1, 2, 3) のように宣言する。リストは可変長、タプルは固定長である。
リストには、[Head|Tail] という構文でリストを分割する機能がある。_(アンダースコア)はワイルドカードで、何にでもマッチングすることを表す。
1 2 3 4 5 6 7 |
|
リストと数値計算
Prolog でリストの合計値を計算する sum を処理するには、次のようにする。
1 2 3 4 5 |
|
合計のルールとして、「空のリストの合計は0であり、Tail の合計(Sum)と Head を足したものが Total になる」ということを与えてやるだけで、Prolog が合計の出してくれる。
Prolog が活躍する場面
Prolog によるプログラミングはまず知識ベースを構築し、その問題領域に関する質問をすることで行う。一部の質問は表明であり、yes、no で答える。変数を含む質問を行うことで、その変数に入る値の組み合わせを求めることができる。
単純な代入はなく、ユニフィケーションとバックトラックという技法を使用して、変数のとりうる値の推論を行っていく。
- 自然言語処理
- ゲームの解法
- セマンティックWeb
- 人工知能
- スケジューリング
知識ベース、ルールが与えられ解を求めるといったようなコンテキストに置いて、Prolog は力を発揮する。
Scala
Scala は異なるプログラミングパラダイム間の橋渡しをする言語である。主に Java との橋渡しをする。
- Java 仮想マシン上で動作するため、既存の環境で共存できる。
- Java のライブラリを使用できる。また、Java のフレームワークも利用出来る。
- 静的に型付けされた言語である。オブジェクト指向と関数型プログラミング言語のパラダイムを持つ、マルチパラダイム言語。
参考
Scala の特徴
- 型推論
Scala は出来る限り変数の型を推論する。
- 関数型概念
既存の関数をさまざまな方法で用いて新しい関数を作ることができる。
- 変更不能な変数
Scala は、変数は変更不可能な val と変更可能な var を使い分ける。
- アクター理論
マルチコア時代に対応した並行処理の仕組みを持つ。
Scala の型
Scala では全てはクラスのインスタンスであり、Java のプリミティブ型も Scala ではオブジェクトとして扱う。ただし、メソッドはオブジェクトではない。関数はクラスのインスタンスであるのでオブジェクトである。
Scala は型推論によってほとんどの変数の型を自動的に解決する。これはコンパイル時に行われる。
Scala にはタプルが用意されている。タプルは固定長のオブジェクトリストのことで、それぞれのオブジェクトは型が違っていても構わない。純粋な関数型言語ではオブジェクトとその属性をタプルで表すことが多い。
Scala のルートクラスには Any という型がある。Scala のすべてのクラスは Any を継承している。Scala にはすべてのクラスのサブクラスである Nothing 型もある。
クラス定義
Scala でクラスを定義する場合は次のように記述する。
1 2 3 4 5 6 7 8 9 10 11 |
|
クラスの定義はコンストラクタになる。クラス名に続いてコンストラクタに渡す引数を記述する。
クラスメソッドの定義
Java ではクラスメソッドを定義するのに static を利用するが、Scala ではクラスメソッドはシングルトンオブジェクトのインスタンスメソッドとして定義する。
インスタンスを一つしか持たないシングルトンクラスを定義するには object キーワードを使う。
1 2 3 |
|
Scala では class 定義と object 定義に同じ名前を使うことができる。クラスメソッドを定義したいときは class 定義で使った名前と同じクラス名を object で使い、クラスメソッドを object 定義内に記述する。
このような class と object で同名のクラスを持つようなオブジェクトをコンパニオンオブジェクトと呼ぶ。
トレイト
Java のインターフェースを Scala ではトレイトと呼ぶ。トレイトには実装も記述することができる。
トレイトはクラスを部分的に実装したものと考えることができ、単一の関心事を実装するのに使うのがよい。
1 2 3 |
|
変更不可能な変数
Scala は並行プログラミングを重視しているため、変更不可能な変数の定義が簡単に行える。Java では final を付けて変数を宣言するが、Scala では val キーワードで変数を定義する。
Scala では可変状態は有害であり、変数は衝突状態をさけるために変更不能(immutable:イミュータブル)にすることが推奨される。
オブジェクト指向プログラミングでは状態はオブジェクトにカプセル化されており、可変であることが多いが、関数型プログラミングの設計哲学では可変状態は並行性を制限するため有害であるとしている。
nil の扱い
Scala では Null はトレイトであり null は Null のインスタンスである。Nil は空のリストになっている。
高階関数
高階関数とは、他の関数を入力として受け取る関数、または出力として関数を返す関数のこと。
Scala のコレクションには foreach という関数を引数にとり繰り返し処理するメソッドが用意されている。
1 2 3 4 |
|
foreach の引数 (f: Elem => U)
の部分が関数を受け取ることを表している。Scala では関数の入力を 入力の型 => 出力の型 という形で表す。
上記の場合、Elem 型のオブジェクトを引数にとり型パラメータ U を返す関数を表す。foreach は例えば次のように利用する。
1 2 3 4 5 |
|
foreach に、無名関数を作成して渡している。無名関数のコードブロックは 変数名 => コード の形で作成する。ここでは、引数 elem に String 型のオブジェクト(list の要素)が渡され、コードの部分が実行される。
アクターとメッセージパッシング
Scala は並行性を実現するのにアクターとメッセージパッシングを利用する。アクターは厳密に管理されたキューで構成され、状態を更新したりアクセスしたりするときには必ずメッセージ交換に寄って通信する。
Scala でアクターを利用する場合は react または receive メソッドを loop でラップした形をしている。
参考
多忙な Java 開発者のための Scala ガイド: Scala の並行性を掘り下げる - developerWorks
Erlang
Erlang(アーラン)は、並行処理指向言語で、スケーラブルな並行性と信頼性を備えている。Erlang は関数型言語で、最大の特徴はプロセスをできるだけ軽量にするという軽量プロセスによる、並行処理である。
Erlang には魅力的な機能が備わっている。
- エラー処理機構
- 動的なコード更新メカニズム
- ビットレベルのパターンマッチング
Erlang のモットーは「非防御的」プログラミングと「クラッシュさせろ」である。
Erlang は難しいことを簡単にし、簡単なことを難しくする言語であり。「普通」のプログラムを書くのは簡単ではない。
参考
関数型言語
- プログラムはすべて関数で作成する。オブジェクトは登場しない。
- 関数は通常、入力が同じであれば出力が同じになる。
- 関数は通常、副作用を持たない。
- すべての変数への代入は1回に限られる。
Erlang は純粋な関数型言語ではない。例外が幾つかある。
軽量プロセス
Erlang は軽量プロセスという考え方を採用している。Erlang もアクターを用いて並行処理を実現している。
Scala ではアクターはオブジェクトでありスレッドプールが動作基盤になっていたが、Erlang ではアクターは軽量プロセスである。
信頼性
Erlang の哲学は「クラッシュさせろ」であり、何かエラーがあればすぐにプロセスを強制終了させ新しいプロセスを作成することができる。
Erlang はコードのホットスワップ(停止させずにアプリケーションの一部を取り替えること)ができる。
Erlang にはメッセージパッシング、プロセスの生成、プロセスの監視の機能が備わっているため、並行処理を行うのがとても容易になっている。
変数とアトム
Erlang では変数は大文字で始める必要がある。小文字で始めた場合はアトム(定数、シンボル)になる。
1 2 3 4 |
|
パターンマッチング
Erlang では、データ構造をマッチングすることで、変数をタプル内の各値に代入する。
1 2 3 4 5 6 |
|
Erlang では複数のマッチング文と複数の種類のタプルを使うことがよくあるので、上記のように、タプルの先頭にデータ構造を表すアトムを入れておくデータ構造をよく使う。
関数
Erlang は、関数を .erl という拡張子を持つファイルに格納する。ファイルを実行するにはコンパイルが必要になる。コンパイルを行うと .beam という実行ファイルが生成される。コンパイル済みのモジュールは beam という仮想マシン内で動作する。
1 2 3 4 |
|
basic モジュール内に mirror という関数を定義した。mirror/1
は一つの引数を取るという意味。export で外部に公開する関数を指定する。実行するにはコンパイルを行い、次のように呼び出す。
1 2 3 |
|
関数は、モジュール名を修飾して呼び出す必要がある。
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 |
|
関数のコードは Prolog のように記述できる。つまり、マッチングである。複数のマッチングの可能性があるコードは終端を ; で終える。最後のケースは . で終わる。
無名関数
1 2 3 |
|
無名関数は fun というキーワードで定義する。関数は変数に代入することができる。
リスト内包表記
関数型言語で最も重要な関数の一つは map である。map はリスト要素に何かを適用しリストを変形させる。
1 2 3 4 |
|
Erlang は、これと同じことをリスト内包表記と呼ぶ構文で用意している。
1
|
|
リスト内包表記の完全な形式は次のとおり。
- リスト内包表記は [式 || 節1, 節2, …, 節N] という形式を持つ。
- リスト内包表記には任意の数の節を含めることができる。
- 節には、ジェネレータまたはフィルタを指定できる。
- フィルタには、ブール式またはブール値を返す関数を指定できる。
-
Match <- List
という形式のジェネレータは、左辺のパターンを右辺のリストの各要素とマッチングする。
1 2 |
|
並行性を実現するプリミティブ
Erlang では並行性を実現する基本プリミティブは、メッセージの送信(!を使用)、プロセスの生成(spawn を使用)、メッセージの受信(receive)の3つになる。
非同期通信
非同期プロセス側の受信ロジックの例を次に示す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
受信は receive で行う。無限ループを実行しているが、Erlang も末尾再帰が最適化されるため loop() がreceive 節の最後の処理である限りオーバーヘッドはない。これが Erlang で無限ループを書く際のイディオムの一つである。
次に、非同期プロセスを生成する側のコードを示す。
1 2 |
|
プロセスの生成は spawn で行う。spawn は関数を引数に取る関数である。
最後に、生成したプロセスに対してメッセージを送るコード例を示す。
1
|
|
プロセスへのメッセージ送信は ! を使う。
同期プロセス
同期プロセスを使う場合は、receive でプロセスのIDと受け取ったメッセージが対となるタプルをマッチさせる。このIDにメッセージを送ることで応答を返す。
1 2 3 4 5 |
|
送信側は、応答を待つようにする必要がある。
1 2 3 4 |
|
送信側も、receive を使って応答を待つようにする。
OTP ライブラリ
Erlang は電話会社で開発されたため、主要なライブラリ OTP(Open Telecom Platform)が用意されている。耐障害性、スケーラビリティ、トランザクション整合性、ホットスワップなどの機能が組み込まれている。
(おまけ)処理系のインストール
Erlang を公式サイトからダウンロードしてきて make を行うと下のようなエラーがでた。
Terminal
|
|
Makefile.in に erl_alloc_types のコンパイル方法が書かれていないせいらしい。github にパッチが上がっていたので、Makefile.in を書き換えると上手くコンパイルができた。
Clojure
Clojure は JVM 上で動く Lisp である。
- Lisp はリストの言語である。関数呼び出しでは、リストの最初の要素を関数として、残りの要素を引数として用いる。
- Lisp は自分自身のデータ構造を用いてコードを表現する。「データとしてのコード」(code as data)の思想で設計されておりマクロ機構をもつ。
参考
関数の呼び出し
Clojure は関数呼び出し全体を括弧で囲む。最初の要素は関数名、残りが引数になる。
1 2 3 |
|
リスト、マップ、セット、ベクタ、シーケンス
Clojure では慣用的に、コードにはリストを、データにはベクタを使用する。
リスト
リストは関数として評価されるため、リストでデータを扱う場合は次のようにする。
1
|
|
ベクタ
ベクタは各カッコ([])で表す。ベクタは順序付きのコレクション。
1
|
|
ベクタも関数であるため、引数にインデックスを取ることができる。
1 2 |
|
セット
セットは順序なしのコレクション。#{} で囲んで定義する。
1 2 |
|
マップ
マップはキーと値のセットで {} で囲んで定義する。
1
|
|
Clojure ではカンマを空白として扱うため、空白の代わりにカンマを使ってもいい。
シーケンス
シーケンスは Clojure のコンテナを実装に依存しない形で抽象化したもの。シーケンスを使うとすべてのコレクションを総称的に扱うことができる。
Clojure では先頭に : が付いている単語はキーワードとして扱われる。Clojure のキーワードは Ruby のシンボル、Prolog や Erlang のアトムと同じものである。
変数の定義
Clojure で変数を定義するのには def を使う。
1
|
|
関数の定義
Clojure で関数を定義するには defn を使う。形式は (defn name [params] body)
である。
1
|
|
関数には説明文を指定することもできる。
1 2 3 4 |
|
無名関数の定義
Clojure では無名関数は fn で定義する。(fn [params] body)
の形式になる。# というリーダーマクロを使って簡略化して書くこともできる。リーダーマクロを使うと % がシーケンスの各項にバインドされる。
1 2 3 4 5 |
|
バインディング
関数の引数に実引数の値を代入することをバインディング(束縛)という。Clojure ではバインディングする引数の任意の部分にだけパラメータとしてアクセスする機能がある。それをデストラクチャリング(分配束縛)という。
無視するパラメータには慣習として _ を使う。
1 2 3 4 |
|
let を使えば引数リスト内以外でもデストラクチャリングを起こすことができる。
1 2 |
|
let の第一引数はバインドするシンボルとバインドされる値からなるベクタ。第二引数は式。
再帰
Clojure は JVM の制約のため末尾再帰最適化をサポートしていない。そのため、loop と recur を使って再帰を定義する必要がある。
マクロ展開
Clojure はマクロ展開と呼ばれる段階を経て、コードを実装または解釈する。
Clojure Macro 入門 - Playground of Mine
ソフトウェアトランザクショナルメモリ(STM)
Clojure では並行性をサポートするためにソフトウェアトランザクショナルメモリ(STM)を用いる。参照を作成するのに ref を使う。
1 2 3 4 5 |
|
参照先を参照するのには deref を使う。@ を使ったシンタックスシュガーも用意されている。
参照先の値を書き換えるときには、トランザクション内で実行する必要がある。トランザクションをオープンするのには dosync 関数を使う。
1 2 3 |
|
アトム
他のアクティビティと連携しない共有データは、単にスレッド安全性を保証したいだけの場合が多い。その場合はアトムを使う。アトムを使うとトランザクション外でもデータの変更を許す。
1
|
|
エージェント、フューチャ
アトムと同様にエージェントを使うとデータを非同期に変更できる。エージェントは Io のフューチャと同じで、デリファレンスされた(参照がとりだされた)エージェントの状態は、値が使用可能になるまでブロックされる。
結果が返されるまで待ちたくない場合はフューチャを使う。
もろもろの並行性に関しての参考
clojureでのrefsの実装について - marblejenkaの日記
Haskell
Haskell は純粋関数型プログラミング言語である。Haskell は遅延評価を重視している。
参照
関数の定義
Haskell の関数定義は型指定と実装に分けて指定する。型指定は省略が可能である。
1 2 3 4 |
|
Main という名前のモジュールに double という関数を定義している。Integer 型の引数を取り Integer 型を返すという型定義をしている。この型定義は省略できる。
Haskell のモジュールは関連するコードを同じスコープ内に集めたもの。Main モジュールは特別なモジュールでトップレベルのモジュールになる。
ガードを使った関数の定義
再帰を利用した階乗計算を行う関数を定義する。
1 2 3 4 5 6 |
|
Haskell ではガードは引数の値を制限する条件として使われる。ガードはパターンマッチング代わりに利用される。
タプル
Haskell のタプル(固定要素のコレクション)はカンマで区切った要素を括弧で囲む。
1
|
|
リスト
Haskell のリストは [] を使う。
1 2 3 4 5 |
|
let はローカルスコープ内で変数を関数にバインドする。(h:t) という表記は Prolog の[Head|Tail] 構文と同じでリストを分割する。: はリストを作成するときにも使える
1 2 |
|
リスト内包表記は、Erlang と同じように使える。
1 2 |
|
無名関数
Haskell で無名関数を定義するには (\param1 .. paramn) -> body)
と書く。
1 2 |
|
部分適用関数とカリー化
Haskell のすべての関数は一つの引数を取る。Haskell において2つの引数をとる関数は、1つの引数をとり「1つの引数をとる関数」を返す関数同義である。
このように、関数を返すことですべての関数を1つの引数をとる関数として表現することをカリー化と呼ぶ。
次の例は引数を2つとってそれぞれをかけたものを返す関数である。
1 2 3 |
|
この関数は、次のように動作する。
-
prod 3
を実行して(\y = 3 * y)
という関数を返す -
(\y = 3 * y) 4
を実行して12を得る
クラス
Haskell のクラスは、入力に応じてどの演算が実行可能かを定義したものである。Clojure のプロトコルと同じ。
モナド
モナドは特別なやり方で複数の関数を組み合わせるための方法である。Haskell は純粋関数型言語なので命令形式で問題を表現したりプログラムの実行結果を蓄積したりする処理が難しくなる。モナドは関数をラップして数珠つなぎにする型構成子である。
モナドは基本的には3つの要素で構成される。
- コンテナとなるものの型を変数に取る型構成子。どのコンテナを選ぶかはモナドで何を実行するかによって異なる。
- 関数をラップしてコンテナに格納する return という名前の関数。
- 関数をラップする >>=(バインド)と言う名前の関数。バインドを使って関数を数珠つなぎにする。