4

Ruby は純粋に動的な型を持つ言語であるため、メソッドに渡される型に対してどのレベルの期待を持たなければならないかはよくわかりません。たとえば、整数が渡されたときにのみメソッドが機能する場合、それが事実であることを確認するために積極的にチェックする必要がありますか、それともそのような場合に型例外を許可する必要がありますか?

さらに、Ruby コードに関する設計ドキュメントを作成する場合、メソッドが操作する型を指定する適切な方法は何でしょうか? たとえば、Javadocs (通常は設計ドキュメントには使用されません) は、言語自体が静的に型付けされているため、メソッドが操作する型を正確に指定しますが、Ruby ドキュメントは、メソッドの前後の条件について一貫して非常に不正確であるようです。 . Ruby でこの種の形式を指定するための標準的な方法はありますか?

4

4 に答える 4

3

最初に知っておく必要があるのは、クラスタイプの違いです。

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 によく教えられていることが期待されています。eachselfEnumeratorRangeRangesucc<=<=>Comparable<=>-101nil

良い例は次のとおりですStringIO: と同じプロトコルを持っていますが、IOそれを継承しておらず、共通の祖先から継承していません (明らかな を除くObject)。したがって、誰かが をチェックするときIO、私は a を渡すことはできません(テストには非常に便利です)。しかし、オブジェクト AS-IFStringIOを単純に使用IOする場合、それは であり、私は a を渡すことができStringIO、彼らは決して違いを知りません。

もちろん、これは理想的ではありませんが、Java と比較してください。重要な要件と保証の多くは、散文でも指定されています。たとえば、型シグネチャのどこでList.sort、結果のリストがソートされると言っていますか? どこにも!それはJavaDocでのみ言及されています。機能的インターフェースのタイプは何ですか? 繰り返しますが、英語の散文でのみ指定されています。ストリーム API には、非干渉や可変性など、型システムでは捉えられない概念がたくさんあります。

長文で申し訳ありませんが、クラスの違いを理解し、Ruby のようなオブジェクト指向言語でが何であるかを理解することは非常に重要です。

型を扱う最善の方法は、単純にオブジェクトを使用してプロトコルを文書化することです。何かを呼び出したい場合は、call;を呼び出してください。である必要はありませんProc。(1 つには、これは を渡すことができないことを意味しMethod、これは迷惑な制限になります。) 何かを追加したい場合は を呼び出し+、何かを追加したい場合は を呼び出し<<、何かを出力したい場合は を呼び出します。printまたは(後者は、たとえばテストで、 a の代わりに a をputs渡すことができる場合に役立ちますStringIOFile)。オブジェクトが特定のプロトコルを満たしているかどうかをプログラムで判断しようとしないでください。それは無駄です。停止問題を解決することと同じです。YARD ドキュメンテーション システムには、タイプを説明するためのタグがあります。完全に自由形式のテキストです。ただし、提案された型言語があります (プロトコルではなくクラスに重点が置かれすぎているため、特に好きではありません)。

(特定のプロトコルを満たすオブジェクトではなく) 特定のクラスのインスタンスが絶対に必要な場合は、自由に使用できる型変換メソッドがいくつあります。ただし、プロトコルに依存する代わりに特定のクラスが必要になるとすぐに、オブジェクト指向プログラミングの領域を離れることになることに注意してください。

知っておくべき最も重要な型変換メソッドは、単一文字メソッドと複数文字to_Xメソッドです。両者の重要な違いは次のとおりです。

  • オブジェクトが配列、文字列、整数、浮動小数点数などとして「ある程度合理的に」表現to_aできる場合、to_sto_i、 、 などに応答しますto_f
  • オブジェクトが、 、 などのインスタンスと同じタイプArrayの場合、String、、などに応答します。IntegerFloatto_aryto_strto_intto_float

これらのメソッドの両方で、例外が発生しないことが保証されています。(もちろん、それらが存在する場合は、存在しない場合は aNoMethodErrorが発生します。) これらのメソッドの両方について、戻り値が対応するコア クラスのインスタンスになることが保証されます。複数文字のメソッドの場合、変換は意味的に無損失でなければなりません。(「保証されている」と言うとき、私は既存のメソッドについて話していることに注意してください。あなたが独自に書いた場合、これは保証ではなく、あなたが満たさなければならない要件です。方法。)

通常、複数文字のメソッドはより厳密であり、その数ははるかに少なくなります。たとえばnil、空の文字列を「次のように表すことができる」と言うのは完全に合理的ですが、nilIS-AN の空の文字列は にnil応答するto_sが、 には応答しないと言うのはばかげていto_strます。同様に、float はto_i切り捨てを返すことで に応答しto_intますが、float を整数に無損失で変換することはできないため、 には応答しません。

Ruby API の例を次に示します。Arrays は、実際にはオブジェクト指向の原則を使用して実装されていません。パフォーマンス上の理由から、Ruby はチートします。その結果、任意の「整数のような」オブジェクトではなく、クラスの実際のインスタンスを使用して にのみインデックスを付けることができますArrayしかし、 を渡さなければならない代わりに、Ruby は最初に を呼び出して、独自の整数のようなオブジェクトを引き続き使用できるようにします。ただし、整数ではないもので配列にインデックスを付けるのは意味がないため、 は呼び出しません。それは「ある程度合理的に表現」することしかできません。OTOH, , , ,と友達が電話IntegerIntegerto_intto_iKernel#printKernel#putsIO#printIO#putsto_sそれらの引数に基づいて、任意のオブジェクトを合理的に印刷できるようにします。Andはその引数をArray#join呼び出しますが、配列要素を呼び出します。それが理にかなっている理由を理解すれば、Ruby の型を理解することにかなり近づきます。to_strto_s

いくつかの経験則を次に示します。

  • タイプをテストせず、ただ使用して文書化する
  • 特定のクラスのインスタンスが絶対に 必要な場合は、おそらく複数文字の型変換を使用する必要があります。クラスをテストするだけでなく、オブジェクトにそれ自体を変換する機会を与える
  • to_s印刷を除いて、単一文字の型変換はほとんどの場合間違っています。文字列や文字列があることに気付かずに静かに変換しnilたり変換したり"one hundred"することが正しいことである状況をいくつ想像できますか?0nil
于 2017-01-12T20:04:22.857 に答える
3

IMOこれはかなり意見に基づいています。また、コンテキストと要件に大きく依存します。自問してみてください:私は気にしますか?エラーを出しても大丈夫ですか?ユーザーは誰ですか (私のコードと外部の顧客)? 入力を修正できますか?

気にしないからすべて問題ないと思います(奇妙な例外が発生する可能性があります)

def add(a, b)
  a + b # raise NoMethodError if a does not respond_to +
end

ダックタイプチェックの使いすぎ

def add(a, b)
  if a.respond_to?(:+)
     a + b
  else
     "#{a} #{b}" # might makes sense?
  end
end 

または単に例外タイプに変換します

def add(a, b)
  a.to_i + b.to_i
end

前もって型をチェックする(そして有用な例外を発生させる):

def integers(a, b)
  raise ArgumentError, "args must be integers" unless a.is_a?(Integer) and b.is_a?(Integer)
  a + b
end

それは本当にあなたのニーズとあなたが必要とするセキュリティと安全のレベルに依存します.

于 2017-01-12T17:04:25.207 に答える
0

興味深い質問です。

タイプセーフ

Java と Ruby は正反対です。Ruby では、次のことができます。

String = Array
# warning: already initialized constant String
p String.new
# []

そのため、Java で知っている型安全性をほとんど忘れることができます。

最初の質問については、次のいずれかを実行できます。

  • メソッドが整数以外で呼び出されていないことを確認してください (例: my_method(array.size))
  • メソッドが Float、Integer、または Rational で呼び出さto_iれ、場合によっては入力で呼び出される可能性があることを受け入れます。
  • Float で問題なく動作するメソッドを使用します。たとえば(1..3.5).to_a #=> [1, 2, 3]'a'*2.5 #=> 'aa'
  • 他の何かで呼び出された場合、 を取得する可能性がありNoMethodError: undefined method 'to_i' for object ...、それを処理しようとすることができます (例: rescue)

ドキュメンテーション

メソッドの予想される入力と出力を文書化する最初のステップは、正しい場所 (クラスまたはモジュール) でメソッドを定義し、適切なメソッド名を使用することです。

  • is_prime?ブール値を返す必要があります
  • is_prime?で定義する必要がありますInteger

それ以外の場合、YARDはドキュメントの型をサポートします:

# @param [Array<String, Symbol>] arg takes an Array of Strings or Symbols
def foo(arg)
end
于 2017-01-12T16:49:56.910 に答える
0

メソッドに整数のみを渡す必要がある理由はわかりませんが、値が整数であることをコード全体で積極的にチェックすることはありません。たとえば、整数を必要とする演算を実行している場合、必要な時点で値を型キャストまたは整数に変換し、コメントまたはメソッドヘッダーでその目的を説明します。

于 2017-01-12T16:50:04.323 に答える