関数としてのオブジェクト

2017年2月13日

プログラミングにおいて、オブジェクトの基本的な概念は、データと動作をバンドルすることです。これにより、一連の関連する関数を記述する際に共通のデータコンテキストが提供されます。また、データの操作のためのインターフェースを提供し、オブジェクトがそのデータへのアクセスを制御できるようにすることで、派生データのサポートを容易にし、データの無効な変更を防ぎます。多くの言語では、オブジェクトの定義として機能するクラスを定義するための明示的な構文を提供しています。しかし、第一級関数を備えたクロージャを持つ言語であれば、これらの構成要素を使用して、「関数としてのオブジェクト」パターン(元々はEugene Wallingfordによって記述されました)を使用してオブジェクトを作成できます。

以下は、JavaScriptで関数としてのオブジェクトスタイルを使用して作成された単純な人物オブジェクトの例です。[1]

function createPerson(name) {
  let birthday;
  return {
    name: () => name,
    setName: (aString) => name = aString,
    birthday: () => birthday,
    setBirthday: (aLocalDate) => birthday = aLocalDate,
    age: age,
    canTrust: canTrust,
  };
  function age() {
    return birthday.until(clock.today(), ChronoUnit.YEARS);
  }
  function canTrust() {
    return age() <= 30;
  }
}

関数としてのオブジェクトの外部形式は関数であり、コンストラクタ関数として呼び出されます。呼び出しの結果は、本質的に、メソッドセレクタとして機能する関数のハッシュマップ[2]です。このマップは、クロージャ内の関数の任意の変数の状態をキャプチャし、データが単一の関数呼び出しを超えて持続することを可能にします。この結果のハッシュマップは、古典的なオブジェクトのように扱うことができます。

const kent = createPerson("kent");
kent.setBirthday(LocalDate.parse("1961-03-31"));
const youngEnoughToTrust = kent.canTrust();

古典的なOOの観点から関数としてのオブジェクトを見ると

  • オブジェクトのフィールドは、コンストラクタ関数(name)のパラメータとローカル変数(birthday)によって表されます。
  • オブジェクトのメソッドは、コンストラクタ関数の中にネストされた関数です。オブジェクトメソッドと同様に、これらは互いを自由に呼び出し、これらのローカルスコープの変数(フィールド)内のデータを操作できます。
  • コンストラクタ関数外部からは変数にアクセスできず、データのカプセル化が維持されます。
  • オブジェクトのパブリックメソッドとは、結果のハッシュマップに存在する関数のことです。
  • コンストラクタ関数内にネストされているが、結果のハッシュマップに存在しない関数は、プライベートメソッドです。
  • パブリックメソッドの名前は、結果のハッシュマップのキーであり、コンストラクタ関数内の関数の名前ではありません。混乱を避けるために、キーと関数名を同じにすることを好みます(ただし、必要に応じて関数をエイリアスすることは便利です)。[3]

このパターンの一般的な代替実装としては、JavaScriptで自然なメソッドセレクタであるハッシュマップではなく、関数としてメソッドセレクタを返す方法があります。関数としてメソッドセレクタを使用するには、呼び出すメソッドの名前を最初の引数とする関数を返します。関数の本体は、その値で切り替えます(詳細についてはWallingfordを参照してください)。

関数としてのオブジェクトのアプローチは長年にわたって存在しており、Lispで何度も記述されているのを見てきましたし、JavaScriptでも広く使用されてきました(ES6以前のJavaScriptでは、クラスの概念は非常に限られていました)。これは、クラスの特定の構文は必要ないという主張としてよく使用されます。これは、オブジェクト愛好家が、単一の「call」メソッドを持つクラスを記述できる場合に、第一級関数は必要ないと主張することに相当します。その結果、JavaScriptの世界の多くの人がES6のクラス構文の使用に反対しています。個人的には、第一級関数と第一級クラスの両方が好きで、ES6のクラス構文の方が好きです。

参考資料

Eugene Wallingfordは、彼の1999年のパターン言語「Envoy」で「関数としてのオブジェクト」という名前を付けました。彼の論文は、関数としてのメソッドセレクタの使用や、継承の概念をある程度サポートするための委任を含む、この点に関する詳細を読む価値があります。論文の例はSchemeを使用しています。

謝辞

Chris Ford、Fred George、James Shore、Kevin Yeung、Lucas Lego、Matteo Vaccari、Rob Miles、およびEugene Wallingfordがこの投稿の下書きについてコメントしてくれました。

注記

1: 日付の処理には、Javaの日付と時刻の処理のひどい混乱を解消したJoda-Timeライブラリの移植版であるjs-jodaを使用しています。joda-jsが日付と時刻の処理に健全性をもたらすサービスを繰り返していることを嬉しく思います。

2: JavaScriptの用語ではオブジェクトと呼ばれますが、作成しようとしている古典的なオブジェクトではなく、JavaScriptオブジェクトです。したがって、混乱を減らすために、ハッシュマップと呼びます。

3: ES6では、「age: age,」を「age,」に置き換えることで、簡略化されたプロパティ名を使用して重複を削除できます。