忘れられたOOPの歴史
注意:これは、JavaScript ES6+で関数プログラミングと構成ソフトウェア技術を一から学ぶ「Composing Software」シリーズ(現在は書籍になっています!)の一部です。 ご期待ください。 まだまだ続きますよ!
Buy the Book | Index | < Previous | Next >
我々が今日使用している関数プログラミングと命令プログラミングパラダイムは1930年代にラムダ計算とチューリングマシンで初めて数学的な探求が行われたものである。 これらは、普遍的計算(一般的な計算を行うことができる形式的なシステム)の代替的な定式化である。
注意: チューリング機械は計算可能なものは何でも計算できるという一般的な誤解があります。 いくつかのケースでは計算可能ですが、チューリング マシンを使用してすべてのケースで一般に計算可能ではない問題のクラス (例: ハーティング問題) があります。
ラムダ計算がトップダウン、関数アプリケーションの計算へのアプローチを表すのに対し、チューリング マシンのティッカー テープ/レジスタ マシンの定式化は、ボトムアップ、命令(ステップバイステップ)の計算へのアプローチを表します。
1940年代にはマシンコードやアセンブリなどの低レベル言語が登場し、1950年代末には最初の一般的な高レベル言語が現れました。 Lispの方言は、Clojure、Scheme、AutoLISPなど、今日でもよく使われています。 FORTRAN と COBOL はどちらも 1950 年代に登場し、現在も使用されている命令型高級言語の例ですが、ほとんどのアプリケーションでは C 系言語が COBOL と FORTRAN の両方を置き換えます。
命令型プログラミングと関数型プログラミングはどちらも、デジタルコンピューターよりも前に、計算理論の数学にルーツを持っています。
「オブジェクト指向プログラミング」 (OOP) は、1966 年または 1967 年頃、大学院に在籍していた Alan Kay によって造語されました。 これは、1961 年から 1962 年の間に作成され、1963 年に彼の Sketchpad Thesis (スケッチパッドの論文) で発表されました。 オブジェクトはオシロスコープ画面に表示されるグラフィックイメージを表すデータ構造で、動的なデリゲートによる継承を特徴とし、Ivan Sutherland は彼の論文の中で「マスター」と呼んでいました。 どんなオブジェクトでも「マスター」になることができ、さらにそのオブジェクトのインスタンスを「オカレンス」と呼んだ。 Sketchpad のマスターは JavaScript のプロトタイプ継承と多くの共通点があります。
注意: MIT Lincoln Laboratory の TX-2 は、軽いペンによる直接画面操作を採用したグラフィック コンピューター モニターの初期の使用例の 1 つでした。 1948 年から 1958 年にかけて稼働していた EDSAC は、スクリーン上にグラフィックを表示することができました。 MITのWhirlwindは、1949年にオシロスコープディスプレイが稼動していた。 プロジェクトの動機は、複数の航空機の計器フィードバックをシミュレートできる一般的なフライトシミュレーターを作ることであった。 それが、SAGE計算機システムの開発につながった。
「オブジェクト指向」として広く認知された最初のプログラミング言語は、1965年に規定されたSimula (シミュラ) です。
注意: 仮想メソッドは、サブクラスによってオーバーライドされるように設計された、クラス上で定義されたメソッドです。 仮想メソッドは、実行時に呼び出す具体的なメソッドを決定する動的ディスパッチを採用することにより、コードがコンパイルされた時点では存在しないかもしれないメソッドを、プログラムが呼び出すことを可能にします。 JavaScript は動的な型を持ち、デリゲーションチェーンを使って呼び出すメソッドを決定するため、仮想メソッドの概念をプログラマに公開する必要がありません。
“I made up the term ‘object-oriented’, and I can tell you not have C++ in mind.” (私は「オブジェクト指向」という言葉を作りましたが、C++ を念頭に置いていなかったと言えます)。 ~ Alan Kay, OOPSLA ’97
Alan Kay は、1966 年か 1967 年の大学院で「オブジェクト指向プログラミング」という言葉を作りました。
「再帰的な設計の基本原則は、部分と全体が同じ力を持つようにすることである」。 ~ Algol-60 を動かすために最適化されたメインフレームである B5000 のメインデザイナー、ボブ・バートン
スモールトークは、ゼロックス PARC でアラン・ケイ、ダン・インガルス、アデル・ゴールドバーグらによって開発されました。 Smalltalk は Simula よりもオブジェクト指向で、クラス、整数、ブロック (クロージャ) など、Smalltalk のすべてがオブジェクトになります。 オリジナルのSmalltalk-72はサブクラス化を備えていませんでした。
Smalltalk はクラスと最終的にはサブクラス化をサポートしましたが、Smalltalk はクラスやサブクラス化についてではありませんでした。 それは、Simula と同様に Lisp に触発された関数型言語だったのです。
「私がずっと以前にこのトピックのために「オブジェクト」という用語を作ったのは、それが多くの人をあまりよくないアイデアに集中させるからだ、と反省しています。 大きなアイデアはメッセージングです。」
~ Alan Kay
2003 年のメール交換で、Alan Kay は Smalltalk を「オブジェクト指向」と呼んだときの意味を明らかにしました
“OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.” (「私にとっての OOP とは、メッセージング、ローカルの保持、状態プロセスの保護と隠蔽、およびすべてのものの極端な遅延結合を意味します。”
~ Alan Kay
つまり、Alan Kay によると、OOP の本質的な要素は次のとおりです。
- メッセージ パッシング
- カプセル化
- 動的結合
注目すべきは、継承とサブクラス多相性は、この用語を作り出し OOP を大衆にもたらした Alan Kay によって OOP に必須の要素とは考えられていない、ということです。
The Essence of OOP
メッセージ パッシングとカプセル化の組み合わせは、次のような重要な目的を果たします:
- 状態をカプセル化して、ローカル状態の変更から他のオブジェクトを分離することにより、変更可能な状態の共有を回避する。 他のオブジェクトの状態に影響を与える唯一の方法は、メッセージを送信することによって、そのオブジェクトに状態を変更するように依頼する (コマンドではない) ことです。 状態の変更は、共有アクセスにさらされるのではなく、ローカルのセルラー レベルで制御されます。
- オブジェクトの相互分離 – メッセージ送信者は、メッセージング API を通じて、メッセージ受信者に緩く結合されているだけです。
これらのアイデアは、アラン・ケイの生物学のバックグラウンドと Arpanet (インターネットの初期バージョン) の設計からの影響により、生物学的細胞やネットワーク上の個々のコンピューターから着想を得ました。 その初期段階においてさえ、Alan Kay は、個々のコンピューターが生物細胞のように動作し、それぞれ独立した状態で動作し、メッセージ パッシングによって通信する、巨大な分散コンピューター (インターネット) 上で動作するソフトウェアを想像していました。
「セル/ホールコンピューターのメタファーはデータを排除することに気づいた」
~ Alan Kay
「データを排除する」ことによって、Alan Kay はきっと、今日共通のテーマである共有ミュータント状態の問題および共有データによる密結合を認識していたと思われます。
しかし、1960 年代後半、ARPA のプログラマーは、ソフトウェアを構築する前にプログラムのデータ モデル表現を選択する必要性に苛立ちを感じていました。 特定のデータ構造にあまりに緊密に結合されたプロシージャーは、変更に対応できませんでした。
「OOP の要点は、オブジェクトの内部に何があるかを心配する必要がないことです。 異なるマシンや異なる言語で作られたオブジェクトは、互いに会話できるようにすべきです」 ~ Alan Kay
オブジェクトはデータ構造の実装を抽象化して隠すことができます。 オブジェクトの内部実装は、ソフトウェア システムの他の部分を壊すことなく変更することができます。 実際、極端なレイト バインディングでは、まったく別のコンピューター システムがオブジェクトの責任を引き継ぎ、ソフトウェアが動作し続けることも可能です。 一方、オブジェクトは、そのオブジェクトがたまたま内部で使っているデータ構造と連動する標準的なインターフェースを公開することができます。
Alan Kay は、オブジェクトを代数的な構造として捉え、その動作について数学的に証明可能な特定の保証を行いました。「
~ Alan Kay
これは真実であることが証明されており、約束やレンズといった、どちらもカテゴリ理論から着想を得たオブジェクトの基礎を形成しています。
代数的な性質による Alan Kay 氏のオブジェクトのビジョンは、オブジェクトに正式な検証、決定論的な動作、および改善されたテスト容易性を提供します。
プログラマーの専門用語では、代数は関数 (操作) からなる抽象化であり、それらの関数が通過しなければならない単体テスト (公理/方程式) によって強制される特定の法則が伴います。
プログラミングの世界は、OO 言語というコンテキストにおいて、関数型プログラミングと推論された思考の利点を再発見していると言えるかもしれません。
以前の JavaScript や Smalltalk のように、現代のほとんどの OO 言語は、ますます「マルチパラダイム言語」になってきています。 関数型プログラミングとOOPのどちらかを選ばなければならない理由はないのです。
このように多くの特徴を共有していることから、私は、JavaScript は Smalltalk による世界の OOP に対する誤解への復讐であると言うのが好きです。 Both Smalltalk and JavaScript support:
- Objects
- First-class functions and closures
- Dynamic types
- Late binding (functions/methods changeable at runtime)
- OOP without class inheritance
What is essential to OOP (according to Alan Kay)?
- Encapsulation
- Message passing
- Dynamic binding (the ability for the program to evolve/adapt at runtime)
What is non-essential?
- Classes
- Class inheritance
- Special treatment for objects/functions/data
- The
new
keyword - Polymorphism
- Static types
- Recognizing a class as a “type”
If your background is Java or C#, you may be thinking static types and Polymorphism are essential ingredients, but Alan Kay preferred dealing with generic behaviors in algebraic form. For example, from Haskell:
fmap :: (a -> b) -> f a -> f b
This is the functor map signature, which acts generically over unspecified types a
and b
, applying a function from a
to b
in the context of a functor of a
to produce a functor of b
. Functor is math jargon that essentially means “supporting the map operation”. If you’re familiar with .map()
in JavaScript, you already know what that means.
Here are two examples in JavaScript:
// isEven = Number => Boolean
const isEven = n => n % 2 === 0;const nums = ;// map takes a function `a => b` and an array of `a`s (via `this`)
// and returns an array of `b`s.
// in this case, `a` is `Number` and `b` is `Boolean`
const results = nums.map(isEven);console.log(results);
//
The .map()
method is generic in the sense that a
and b
can be any type, and .map()
handles it just fine because arrays are data structures that implement the algebraic functor
laws.
// matches = a => Boolean
// here, `a` can be any comparable type
const matches = control => input => input === control;const strings = ;const results = strings.map(matches('bar'));console.log(results);
//
この一般的な型の関係は、TypeScript のような言語で正確かつ徹底的に表現するのは困難ですが、Haskell の Hindley Milner 型では、より高い kinded 型 (型の型) のサポートによりかなり簡単に表現することが可能でした。
ほとんどの型システムは、関数合成、自由オブジェクト合成、実行時オブジェクト拡張、コンビネーター、レンズなど、動的および関数的なアイデアを自由に表現することを可能にするために、あまりにも制限的なものでした。
型システムがあまりにも制限的である場合 (たとえば、TypeScript、Java)、同じ目標を達成するために、より複雑なコードを書かざるを得なくなります。 だからといって、静的型が悪い考えであるとか、すべての静的型の実装が同じように制限的であるとかいうわけではありません。
あなたが静的型のファンで、制限を気にしないのであれば、それはそれで良いのですが、合成関数や複合代数構造を型付けするのが難しいので、このテキストのいくつかのアドバイスが難しいと感じた場合は、アイデアではなく、型システムのせいにしてください。 人々はSUVの快適さを愛しているが,SUVが空を飛べないことに文句を言う人はいない.
制限によってコードがよりシンプルになるなら、それは素晴らしいことです。 しかし、制限がより複雑なコードを書かせるのであれば、おそらく制限は間違っています。
オブジェクトとは
オブジェクトは明らかに、長年にわたって多くの意味合いを帯びてきました。 JavaScript で「オブジェクト」と呼んでいるものは、単に複合データ型であり、クラス ベースのプログラミングまたは Alan Kay のメッセージ パッシングのいずれからも影響を受けません。
JavaScript では、これらのオブジェクトはカプセル化、メッセージ パッシング、メソッドによる動作の共有、さらにはサブクラス ポリモーフィズム (ただし、型ベースのディスパッチではなく委譲チェーンを使用) をサポートでき、頻繁に実行されます。 どんな関数でもどんなプロパティに割り当てることができます。 オブジェクトの振る舞いを動的に構築し、実行時にオブジェクトの意味を変更することができる。 JavaScript は実装のプライバシーを守るためにクロージャを使ったカプセル化もサポートしています。
私たちの現在のオブジェクトの考えは、単に複合データ構造であり、オブジェクトと見なされるためにそれ以上何かを必要としません。
OOP is not Real OOP Anymore
現代のプログラミング言語における「オブジェクト」は、Alan Kay にとっての意味よりはるかに小さいので、私は「オブジェクト」の代わりに「コンポーネント」を使用して、本当の OOP のルールを説明します。 多くのオブジェクトは、JavaScript の他のコードによって直接所有され、操作されますが、コンポーネントはカプセル化され、自身の状態を制御する必要があります。
本当の OOP とは
- コンポーネント (Alan Kay の「オブジェクト」) によるプログラミング
- コンポーネントの状態はカプセル化されていなければならない
- オブジェクト間の通信にメッセージ パッシングを使用する
- コンポーネントは実行時に追加/変更/置換できる
ほとんどのコンポーネント動作は代数的データ構造を使用して一般に指定することが可能である。 ここでは、継承は必要ありません。
オブジェクトを操作したり、JavaScript でクラス継承を使用したりすることは、「OOP を行っている」ことを意味しません。 この方法でコンポーネントを使用すると、そうなります。
モップがゴミを片付けるのに使われるのは偶然ですか
現代のソフトウェアの多くでは、ユーザーとの対話を管理する UI、アプリケーションの状態 (ユーザー データ) を管理するコード、システムまたはネットワーク I/O を管理するコードが存在します。
これらのシステムのそれぞれは、ネットワーク接続、UI 要素の状態、およびアプリケーションの状態そのものを追跡するために、イベント リスナー、状態などの長期間のプロセスを必要とする場合があります。
優れた MOP とは、これらのシステムのすべてが手を伸ばして互いの状態を直接操作するのではなく、システムがメッセージ ディスパッチによって他のコンポーネントと通信することを指します。 ユーザーが保存ボタンをクリックすると、"SAVE"
"STATE_UPDATED"
メッセージを UI コンポーネントにディスパッチし、UI コンポーネントは状態を解釈し、更新が必要な UI の部分を調整し、UI のそれらの部分を処理するサブコンポーネントに更新状態をリレーします。
一方、ネットワーク接続コンポーネントはネットワーク上の別のマシンへのユーザーの接続を監視し、メッセージを聞き、リモート マシンのデータを救うために更新状態表現をディスパッチすることができます。
これらのシステムは、システムの他の部分の詳細について知る必要はありません。 個々のモジュール化された関心事についてのみです。 システム コンポーネントは分解可能であり、再コンポーザブルです。 これらのコンポーネントは、相互運用が可能なように、標準化されたインターフェイスを実装しています。 インターフェイスが満たされている限り、同じことを異なる方法で行う代替品や、同じメッセージで全く異なることを行う代替品で代用することができる。
同じソフトウェア システムのコンポーネントは、同じマシンにある必要はないかもしれません。 システムは分散化されるかもしれません。
OOPは部分的にArpanetに触発されており、Arpanetの目標の1つは、原子爆弾のような攻撃に対して回復力のある分散型ネットワークを構築することでした。 Arpanet 開発時の DARPA のディレクターである Stephen J. Lukasik (“Why the Arpanet Was Built”) によると、
「目標は、新しいコンピューター技術を利用して、核の脅威に対する軍事指揮統制のニーズを満たし、米国の核軍の生存可能なコントロールを達成し、軍の戦術および管理の意思決定を改善することだった」と述べています。「
注意: Arpanet の主要なきっかけは、核の脅威よりも利便性であり、その明白な防衛上の利点は後に現れました。 ARPA は、3 つの別々のコンピューター研究プロジェクトと通信するために、3 つの別々のコンピューター端末を使用していました。
優れた MOP システムは、アプリケーションの実行中にホットスワップ可能なコンポーネントを使用して、インターネットの堅牢性を共有することができます。
優れた MOP システムは、アプリケーションの実行中にホットスワップ可能なコンポーネントを使用して、インターネットの堅牢性を共有することができます。
ソフトウェアの世界では、失敗したクラス継承の実験をやめ、元々 OOP の精神を定義した数学と科学の原則を受け入れる時期に来ています。
今こそ、MOP と関数型プログラミングが調和し、より柔軟で弾力性のある、優れた構成のソフトウェアの構築を始める時です。
注意: MOP という頭字語はすでに「モニタリング指向プログラミング」を表すのに使われており、OOP が静かに去っていくことはないでしょう。
Learn More at EricElliottJS.com
関数型プログラミングに関するビデオレッスンは EricElliottJS.com のメンバー向けに提供されています。 まだメンバーでない方は、今すぐサインアップしてください。