最初に知っておく必要があるのは、クラスとタイプの違いです。
Java がクラスを常に型にすることでこの区別を混乱させているのは非常に残念なことです (Java にはクラス以外の型、つまりインターフェイス、プリミティブ、ジェネリック型パラメーターがありますが)。実際、Java スタイルに関するほとんどすべての本では、クラスを型として使用しないように指示されています。また、William R. Cook は、影響力のある論文On Understanding Data Abstraction, Revisitedで、Java ではクラスはオブジェクトではなく抽象データ型を記述すると指摘しています。インターフェイスはオブジェクトを記述するため、Java でクラスを型として使用する場合は、OO を行っていません。JavaでOOしたい場合、型として使用できるのはインターフェースだけであり、クラスを使用できるのはファクトリだけです。
Ruby では、型はネットワーク プロトコルに似ています。型は、オブジェクトが理解するメッセージと、それに対する反応を記述します。(この類似性は偶然ではありません。Ruby の遠い祖先である Smalltalk は、後にインターネットになるものに触発されました。Smalltalk の用語では、「プロトコル」は、オブジェクトのタイプを表すために非公式に使用される用語です。Objective-C では、この非公式のプロトコルの概念は言語の一部となり、主に Objective-C の影響を受けた Java はこの概念を直接コピーしましたが、名前を「インターフェース」に変更しました。)
したがって、Ruby では次のようになります。
module
(言語機能): コード共有と差分実装の手段。タイプではありません
class
(言語機能): オブジェクトのファクトリ、タイプmodule
ではなくIS-A も
- プロトコル(非公式のもの):メッセージが応答することと、それらに応答する方法によって特徴付けられるオブジェクトのタイプ
また、オブジェクトは複数のタイプを持つことができることに注意してください。たとえば、文字列オブジェクトには、「Appendable」( に応答する<<
) と「 Indexable 」( に応答する) の両方のタイプがあり[]
ます。
したがって、重要な点を要約すると、次のようになります。
- 型は Ruby 言語には存在せず、プログラマーの頭の中だけに存在します
- クラスとモジュールは型ではありません
- 型はプロトコルであり、オブジェクトがメッセージに応答する方法によって特徴付けられます
明らかに、プロトコルは言語で指定できないため、通常はドキュメントで指定されています。多くの場合、それらはまったく指定されていません。これは実際には思ったほど悪くはありません。多くの場合、メッセージ送信の引数に課される要件は、名前やメソッドの使用目的から「明らか」です。また、一部のプロジェクトでは、ユーザー向けの受け入れテストがその役割を果たすことが期待されています。(たとえば、もはや存在しない Merb Web フレームワークの場合がそうでした。API は、受け入れテストで完全に記述されていました。) 間違った型を渡したときに発生するエラー メッセージと例外も、多くの場合、メソッドが何を意味するかを理解するのに十分です。必要。最後になりましたが、常にソース コードがあります。
いくつかのよく知られたプロトコルがあります。たとえば、each
混合によって必要とされるプロトコルです(オブジェクトは、要素を 1 つずつ生成し、ブロックが渡された場合は戻り、ブロックが渡されなかった場合は を返すことによってEnumerable
応答する必要があります)。 )、オブジェクトが のエンドポイントになりたい場合に必要なプロトコル(後続のもので応答する必要があり、 に応答する必要がある)、または混合によって必要なプロトコル(オブジェクトは、、、 のいずれかで応答する必要がある)または)。これらはまた、どこにも書き留められていないか、断片的にしか書かれておらず、既存の Rubyist によく知られており、新しい Rubyist によく教えられていることが期待されています。each
self
Enumerator
Range
Range
succ
<=
<=>
Comparable
<=>
-1
0
1
nil
良い例は次のとおりですStringIO
: と同じプロトコルを持っていますが、IO
それを継承しておらず、共通の祖先から継承していません (明らかな を除くObject
)。したがって、誰かが をチェックするときIO
、私は a を渡すことはできません(テストには非常に便利です)。しかし、オブジェクト AS-IFStringIO
を単純に使用IO
する場合、それは であり、私は a を渡すことができStringIO
、彼らは決して違いを知りません。
もちろん、これは理想的ではありませんが、Java と比較してください。重要な要件と保証の多くは、散文でも指定されています。たとえば、型シグネチャのどこでList.sort
、結果のリストがソートされると言っていますか? どこにも!それはJavaDocでのみ言及されています。機能的インターフェースのタイプは何ですか? 繰り返しますが、英語の散文でのみ指定されています。ストリーム API には、非干渉や可変性など、型システムでは捉えられない概念がたくさんあります。
長文で申し訳ありませんが、クラスと型の違いを理解し、Ruby のようなオブジェクト指向言語で型が何であるかを理解することは非常に重要です。
型を扱う最善の方法は、単純にオブジェクトを使用してプロトコルを文書化することです。何かを呼び出したい場合は、call
;を呼び出してください。である必要はありませんProc
。(1 つには、これは を渡すことができないことを意味しMethod
、これは迷惑な制限になります。) 何かを追加したい場合は を呼び出し+
、何かを追加したい場合は を呼び出し<<
、何かを出力したい場合は を呼び出します。print
または(後者は、たとえばテストで、 a の代わりに a をputs
渡すことができる場合に役立ちますStringIO
File
)。オブジェクトが特定のプロトコルを満たしているかどうかをプログラムで判断しようとしないでください。それは無駄です。停止問題を解決することと同じです。YARD ドキュメンテーション システムには、タイプを説明するためのタグがあります。完全に自由形式のテキストです。ただし、提案された型言語があります (プロトコルではなくクラスに重点が置かれすぎているため、特に好きではありません)。
(特定のプロトコルを満たすオブジェクトではなく) 特定のクラスのインスタンスが絶対に必要な場合は、自由に使用できる型変換メソッドがいくつかあります。ただし、プロトコルに依存する代わりに特定のクラスが必要になるとすぐに、オブジェクト指向プログラミングの領域を離れることになることに注意してください。
知っておくべき最も重要な型変換メソッドは、単一文字メソッドと複数文字to_X
メソッドです。両者の重要な違いは次のとおりです。
- オブジェクトが配列、文字列、整数、浮動小数点数などとして「ある程度合理的に」表現
to_a
できる場合、to_s
、to_i
、 、 などに応答しますto_f
。
- オブジェクトが、 、 などのインスタンスと同じタイプ
Array
の場合、String
、、などに応答します。Integer
Float
to_ary
to_str
to_int
to_float
これらのメソッドの両方で、例外が発生しないことが保証されています。(もちろん、それらが存在する場合は、存在しない場合は aNoMethodError
が発生します。) これらのメソッドの両方について、戻り値が対応するコア クラスのインスタンスになることが保証されます。複数文字のメソッドの場合、変換は意味的に無損失でなければなりません。(「保証されている」と言うとき、私は既存のメソッドについて話していることに注意してください。あなたが独自に書いた場合、これは保証ではなく、あなたが満たさなければならない要件です。方法。)
通常、複数文字のメソッドはより厳密であり、その数ははるかに少なくなります。たとえばnil
、空の文字列を「次のように表すことができる」と言うのは完全に合理的ですが、nil
IS-AN の空の文字列は にnil
応答するto_s
が、 には応答しないと言うのはばかげていto_str
ます。同様に、float はto_i
切り捨てを返すことで に応答しto_int
ますが、float を整数に無損失で変換することはできないため、 には応答しません。
Ruby API の例を次に示します。Array
s は、実際にはオブジェクト指向の原則を使用して実装されていません。パフォーマンス上の理由から、Ruby はチートします。その結果、任意の「整数のような」オブジェクトではなく、クラスの実際のインスタンスを使用して にのみインデックスを付けることができますArray
。しかし、 を渡さなければならない代わりに、Ruby は最初に を呼び出して、独自の整数のようなオブジェクトを引き続き使用できるようにします。ただし、整数ではないもので配列にインデックスを付けるのは意味がないため、 は呼び出しません。それは「ある程度合理的に表現」することしかできません。OTOH, , , ,と友達が電話Integer
Integer
to_int
to_i
Kernel#print
Kernel#puts
IO#print
IO#puts
to_s
それらの引数に基づいて、任意のオブジェクトを合理的に印刷できるようにします。Andはその引数をArray#join
呼び出しますが、配列要素を呼び出します。それが理にかなっている理由を理解すれば、Ruby の型を理解することにかなり近づきます。to_str
to_s
いくつかの経験則を次に示します。
- タイプをテストせず、ただ使用して文書化する
- 特定のクラスのインスタンスが絶対に 必要な場合は、おそらく複数文字の型変換を使用する必要があります。クラスをテストするだけでなく、オブジェクトにそれ自体を変換する機会を与える
to_s
印刷を除いて、単一文字の型変換はほとんどの場合間違っています。文字列や文字列があることに気付かずに静かに変換しnil
たり変換したり"one hundred"
することが正しいことである状況をいくつ想像できますか?0
nil