異種リストに特定の目的はありますか?


13

C#とJavaのバックグラウンドから来た私は、リストが同種であることに慣れていて、それは私にとって理にかなっています。私がLispを取り上げ始めたとき、リストが異種であることに気付きました。dynamicC#でキーワードをいじり始めたとき、C#4.0の時点で、異種のリストが存在する可能性があることに気付きました。

List<dynamic> heterogeneousList

私の質問は、ポイントは何ですか?異種リストは、処理を行う際のオーバーヘッドがはるかに大きくなるようであり、1つの場所に異なる型を格納する必要がある場合は、異なるデータ構造が必要になる場合があります。私の素朴さはそのい顔を育てているのですか、それとも異種のリストを持つことが本当に役立つときがありますか?


1
もしかして...私はリストが不均一になることに気付きました...?
ワールドエンジニア

List<dynamic>(あなたの質問に関して)単純に行うこととどう違うのList<object>ですか?
ピーターK.

@WorldEngineerはい、そうです。投稿を更新しました。ありがとう!
ジェッティ

@PeterK。毎日の使用では、違いはないと思います。ただし、C#のすべての型がSystem.Objectから派生するわけではないため、違いがあるエッジケースがあります。
ジェッティ

回答:


16

Oleg Kiselyov、RalfLämmel、およびKeean Schupkeによる「Strongly Typed Heterogenous Collections」という論文には、Haskellでの異種リストの実装だけでなく、HListをいつ、なぜ、どのように使用するかを示す動機付けの例も含まれています。特に、タイプセーフなコンパイル時のチェック済みデータベースアクセスに使用しています。(実際に、LINQが考えているの、彼らが参照している論文、LINQにつながったErik MeijerらによるHaskellの論文です。)

HLists論文の序文からの引用:

以下は、異種コレクションを必要とする典型的な例の無制限のリストです。

  • 異なるタイプのエントリを格納することになっているシンボルテーブルは、異種です。これは有限のマップであり、結果の型は引数の値に依存します。
  • XML要素は異機種混合です。実際、XML要素は、正規表現と1-ambiguityプロパティによって制約されるネストされたコレクションです。
  • SQLクエリによって返される各行は、列名からセルへの異種マップです。クエリの結果は、異種行の同種ストリームです。
  • 高度なオブジェクトシステムを関数型言語に追加するには、拡張可能なレコードとサブタイピングおよび列挙インターフェイスを組み合わせた種類の異種コレクションが必要です。

質問で与えた例は、単語が一般的に使用されているという意味で、実際には異種のリストではありません。それらは弱い型付けまたは型付けされていないリストです。実際、すべての要素は同じタイプであるため、それらは実際には同種のリストです:objectまたはdynamic。次にinstanceof、実際に要素を有意義に操作できるようにするために、キャストまたは未チェックのテストなどを実行することを余儀なくされます。


リンクとご回答ありがとうございます。あなたは、リストが本当に異種ではなく、弱く型付けされていることを指摘します。私はその論文を読むことを楽しみにしています(おそらく明日、今夜
中旬に

5

長い話で言えば、異種のコンテナーは、実行時のパフォーマンスと柔軟性のトレードオフです。特定の種類のものに関係なく、「もののリスト」が必要な場合は、不均一性を利用する方法があります。Lispは特徴的に動的に型付けされ、ほとんどすべてがとにかくボックス化された値のコンスリストであるため、小さなパフォーマンスのヒットが予想されます。Lispの世界では、プログラマの生産性はランタイムのパフォーマンスよりも重要であると考えられています。

動的に型付けされた言語では、追加されたすべての要素を型チェックする必要があるため、同種のコンテナには異種のコンテナと比較して実際にわずかなオーバーヘッドがあります。

より良いデータ構造を選択することについてのあなたの直感は適切です。一般的に言えば、コードに配置できるコントラクトが多いほど、その動作方法がわかりやすくなり、信頼性、保守性が向上します。あれは。。。になる。しかし、時にはあなたは本当に異質コンテナをしたい、とあなたがそれを必要とする場合は、1つを持つことを許可されるべきです。


1
「ただし、異種コンテナが本当に必要な場合があり、必要な場合はコンテナを許可する必要があります。」-なんで?それが私の質問です。大量のデータをランダムなリストに入れる必要があるのはなぜですか?
ジェッティ

@Jetti:ユーザーが入力したさまざまなタイプの設定のリストがあるとします。インターフェースIUserSettingを作成して何度か実装するか、ジェネリックを作成することができUserSetting<T>ますが、静的型付けの問題の1つは、使用方法を正確に知る前にインターフェースを定義していることです。整数設定で行うことは、文字列設定で行うこととおそらく非常に異なるので、共通のインターフェイスに入れるのに意味のある操作は何ですか?確実にわかるまでは、動的型付けを慎重に使用し、後で具体的にする方が良いでしょう。
ジョンパーディ

それが私が問題に遭遇するところを見てください。私には、それは悪いデザインのように思え、何をするか/使う前に何かを作る前に何かを作ります。また、その場合、オブジェクトの戻り値を持つインターフェイスを作成できます。異種混合リストと同じことをしますが、インターフェイスで使用される型が特定されていることがわかったら、より明確で簡単に修正できます。
ジェッティ

@Jetti:それは本質的に同じ問題です。しかし、普遍的な基底クラスはそもそも存在すべきではありません。なぜなら、それが定義するオペレーションに関係なく、それらのオペレーションが意味をなさないタイプがあるからです。ただし、C#objectでaではなくa を使用する方が簡単なdynamic場合は、前者を使用してください。
ジョンパーディ

1
@Jetti:これがポリモーフィズムです。リストには、単一のスーパークラスのサブクラスであっても、多数の「異種」オブジェクトが含まれます。Javaの観点から、クラス(またはインターフェース)定義を正しく取得できます。他の言語(LISP、Pythonなど)については、実際の実装の違いがないため、すべての宣言を正しくするメリットはありません。
S.Lott

2

関数型言語(Lispなど)では、パターンマッチングを使用して、リスト内の特定の要素に何が起こるかを判断します。C#に相当するのは、要素の型をチェックし、それに基づいて操作を実行するif ... elseifステートメントのチェーンです。言うまでもなく、機能的なパターンマッチングは、実行時の型チェックよりも効率的です。

ポリモーフィズムの使用は、パターンマッチングにより近いものになります。つまり、リストのオブジェクトを特定のインターフェイスと一致させ、各インターフェイスに対してそのインターフェイスで関数を呼び出します。別の代替方法は、特定のオブジェクトタイプをパラメーターとして受け取るオーバーロードメソッドのシリーズを提供することです。Objectをパラメーターとしてとるデフォルトのメソッド。

public class ListVisitor
{
  public void DoSomething(IEnumerable<dynamic> list)
  {
    foreach(dynamic obj in list)
    {
       DoSomething(obj);
    }
  }

  public void DoSomething(SomeClass obj)
  {
    //do something with SomeClass
  }

  public void DoSomething(AnotherClass obj)
  {
    //do something with AnotherClass
  }

  public void DoSomething(Object obj)
  {
    //do something with everything els
  }
}

このアプローチは、Lispパターンマッチングの近似を提供します。ビジターパターン(ここで実装されているように、異種リストの使用法の良い例です)。別の例は、優先キューに特定のメッセージのリスナーがあり、責任チェーンを使用してメッセージをディスパッチする場合です。ディスパッチャーはメッセージを渡し、メッセージに一致する最初のハンドラーがそれを処理します。

その反対は、メッセージに登録するすべての人に通知することです(たとえば、MVVMパターンでViewModelの疎結合に一般的に使用されるEvent Aggregatorパターンなど)。私は次の構成を使用します

IDictionary<Type, List<Object>>

辞書に追加する唯一の方法は関数です

Register<T>(Action<T> handler)

(そして、オブジェクトは実際に渡されたハンドラへのWeakReferenceです)。そのため、ここでは、コンパイル時にList <Object>を使用する必要があります。これは、閉じた型が何であるかがわからないためです。ただし、実行時には、辞書のキーであるTypeを強制できます。呼び出すイベントを発生させたいとき

Send<T>(T message)

そして再びリストを解決します。とにかくキャストする必要があるため、List <dynamic>を使用する利点はありません。このように、両方のアプローチにはメリットがあります。メソッドのオーバーロードを使用してオブジェクトを動的にディスパッチする場合、ダイナミックがその方法です。関係なくキャストすることを強制される場合は、Objectを使用することもできます。


パターンマッチングでは、ケースは(通常-少なくともMLおよびHaskellで)一致するデータ型の宣言によって明確に設定されます。そのようなタイプを含むリストも異種ではありません。

MLとHaskellについては確信がありませんが、Erlangはいずれにも一致する可能性があります。つまり、他の一致が満たされていないためにここに到達した場合は、これを実行します。
マイケルブラウン

@MikeBrown-これは素晴らしいですが、なぜ異種リストを使用し、List <dynamic>で常に動作するとは限らないのかをカバーしていません
-Jetti

4
C#では、オーバーロードはコンパイル時に解決されます。したがって、サンプルコードは常に呼び出しますDoSomething(Object)(少なくともループで使用objectする場合は、まったく別のものです)。foreachdynamic
ハインジ

@Heinzi、あなたは正しい...私は今日眠いです:P修正
マイケルブラウン

0

異質性はランタイムオーバーヘッドを伴うことは正しいですが、さらに重要なことは、タイプチェッカーによって提供されるコンパイル時の保証を弱めることです。それでも、選択肢がさらに高価になる問題がいくつかあります。

私の経験では、ファイル、ネットワークソケットなどを介して生のバイトを処理する場合、多くの場合、このような問題に遭遇します。

実際の例を示すために、futuresを使用した分散計算のシステムを考えます。個々のノードのワーカーは、任意のシリアル化可能なタイプの作業を生成し、そのタイプの将来を生成できます。舞台裏では、システムが作業をピアに送信し、その作業の単位を、その作業に対する回答が返ってきたら記入する必要がある特定の未来に関連付けるレコードを保存します。

これらの記録はどこに保管できますか?直感的には、のようなものが必要ですがDictionary<WorkId, Future<TValue>>、これはシステム全体で1種類の先物のみを管理することに制限されます。より適切なタイプはDictionary<WorkId, Future<dynamic>>、ワーカーが未来を強制するときに適切なタイプにキャストできるためです。

:この例は、サブタイプを持たないHaskellの世界からのものです。C#でこの特定の例に対してより慣用的な解決策があれば驚くことはありませんが、うまくいけば実例になります。


0

Lispはあなたが集計データオブジェクトの任意の型を必要がある場合に、ために起こっている、リスト以外のデータ構造を持っていないことをISTR 持つ異質なリストであることを。他の人が指摘したように、それらは送信またはストレージのいずれかのデータをシリアル化するのにも役立ちます。優れた機能の1つは、これらもオープンエンドであるため、パイプとフィルターの類推に基づいたシステムで使用でき、固定データオブジェクトまたはワークフロートポロジを必要とせずに、連続する処理ステップでデータを増強または修正できることです。 。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.