プログラムの世界は常に進化していて、激しい変化に晒されています。
過去の大きな変化と言えば、何と言ってもオブジェクト指向プログラミングでしょう。JavaやC#、Rubyと言った現在の有力なプログラミング言語は、ほぼ全てと言っていいくらい、オブジェクト指向を強く意識した仕様になっています。
しかしここ数年くらいで、オブジェクト指向に匹敵するほどの大きな変化が起きています。
それが関数型プログラミングというスタイルです。
このエントリでは関数型プログラミングとは何か、またなぜ重要なのか、という説明を試みます。
関数とは
さて関数とはなんでしょうか。
一般的には
「同じ入力に対して常に同じ出力を返す」ルーチンのことを、関数と呼びます。
例えば、オブジェクト指向でよく見られるセッターメソッドは関数とは呼びません。
class Data {
int number;
void setNumber(int pNumber) {
this.number = pNumber;
}
}
しかし、次のようなユーティリティは関数と呼べるでしょう。
class Add {
static long add(int a, int b) {
retur a + b;
}
}
このメソッドは入力が同じ値なら、常に同じ出力が得られます。これは関数の性質そのものです。
入力が同じなら、常に結果も同じーーーこれが関数型プログラミングの大変重要な性質の1つです。
## オブジェクト指向と比較してみる
この辺りで関数型プログラミングの雰囲気を掴んでもらいたいと思います。
オブジェクト指向言語と比較するために、自動販売機プログラムを作ってみましょう。ただ、フルスペックの自動販売機をプログラムするのは大変なので、次のようなごくシンプルな設計にします。
- 売る物は1つだけ、MacBookAir13インチのみ!価格は12万円に固定!
- お金を入れて、ボタンを押してMacBookAirを売ってもらって、お釣りをもらう。それだけのシンプル自販機
オブジェクト指向言語としてJava、関数型言語としてHaskellを使ってプログラムを作ります。
### Javaの自動販売機
オブジェクト指向のプログラミングスタイルも色々ですが、ここではデータと振る舞いを同じクラスに定義する方針で作ってみます。
package oop;
/**
* 自動販売機クラス.
*/
public class MacBookAirVendingMachine {
private static final int PRICE = 120_000;
private int investedMoney = 0;
/**
* @return 現在投入されている金額.
*/
public int getInvestedMoney() {
return investedMoney;
}
/**
* @param pMoney お金を投入する.
*/
public void investMoney(final int pMoney) {
this.investedMoney += pMoney;
}
/**
* @return お釣りを返す.
*/
public int payBack() {
final int ret = this.investedMoney;
this.investedMoney = 0;
return ret;
}
/**
* @return 購入したことを示すメッセージ.
* @throws MoneyShortage お金が不足している.
*/
public String sell() throws MoneyShortage {
if (this.investedMoney < PRICE) {
throw new MoneyShortage();
}
this.investedMoney -= PRICE;
return "MacBook Air 13インチが販売されました!";
}
}
このクラスの機能は以下の通りです。
- ★お金を投入してもらう
- 今残っている金額を提示する
- ★MacBookAirを売る
- ★お釣りを返す
先頭に★を付けている機能は、オブジェクト内部の状態(=残っている金額)を更新します。
ごく普通のオブジェクト指向プログラムです。さて同じお題を関数型のスタイルで書くとどうなるでしょうか。
Haskellの自動販売機
代表的な関数型プログラミング言語であるHaskellで自動販売機を作ってみましょう。Haskellの文法が分からない方も、なんとなく読み解けると思うので、あまり深く考えずに読み進めて下さい。
関数型では、データと処理を明確に分離するスタイルが一般的です。
今回の自販機では、投入された金額をどう扱うのかがポイントです。
ここでは、次のようなアプローチをとります。
- 自販機の状態をデータとして扱う
- 自販機の操作を「ある状態から別の状態を作り出す関数」として定義する
まずは自販機の状態を表す型の周りを見てみましょう。
{-- Integerに別名を付けてお金を表現する. --}
type Money = Integer
{-- Mac自販機の状態を表現する型. --}
data MacBookAirVendingMachineState = MacBookAirVendingMachineState {
investedMoney :: Money -- 投入してまだ残っている金額
} deriving (Show)
{-- Mac自販機の初期状態. --}
initialState :: MacBookAirVendingMachineState
initialState = MacBookAirVendingMachineState { investedMoney = 0 }
次に操作を見てみましょう。まずはお金を投入する関数です。
{-- 自販機操作関数:お金を投入 --}
investMoney :: MacBookAirVendingMachineState -> Money -> MacBookAirVendingMachineState
investMoney state money = MacBookAirVendingMachineState { investedMoney = (investedMoney state) + money }
お釣りを返す関数。
{-- 自販機操作関数:お釣りを返す --}
payBack :: MacBookAirVendingMachineState -> (Money, MacBookAirVendingMachineState)
payBack state = (investedMoney state, initialState)
最後に、MacBook Airを売る関数です。
type ErrorMessage = Text
type SellingMessage = Text
mbaPrice = 120000
{-- 自販機操作関数:MacBook Airを販売する
- Eitherは2つの状態を表現する型.
- お金が足りないなどのエラーが発生したらLeft <エラーメッセージ>という値、
- 無事販売出来たらRight (<メッセージ>, 販売後の自販機の状態)という値となる.
--}
sell :: MacBookAirVendingMachineState -> Either ErrorMessage (SellingMessage, MacBookAirVendingMachineState)
sell state = if investedMoney state < mbaPrice then
Left "お金が足りません."
else
Right ("MacBook Airを販売しました!", MacBookAirVendingMachineState { investedMoney = investedMoney state - mbaPrice })
いかがでしょうか。1つ1つの関数がとても簡潔なことが分かると思います。
いかがでしょうか。オブジェクト指向とは随分異なるアプローチのプログラムになることに驚かれたのではないでしょうか。
オブジェクト指向では状態は「保持する」もの、関数型では状態は「遷移していく」もの・・・とでも言えばイメージが掴みやすいでしょうか。
さて前編はここまでです。
後編では関数型言語の特徴を追っていくことにします。