web-dev-qa-db-ja.com

データ/オブジェクトの対称性を考慮して拡張性に取り組む方法は?

アンクルボブによるクリーンコードで、彼は述べています。

手続き型コード(データ構造を使用したコード)により、既存のデータ構造を変更せずに新しい関数を簡単に追加できます。 OOコードは、一方で、既存の関数を変更せずに新しいクラスを簡単に追加できます。

マルチディスパッチに関するこの記事 のようなマルチディスパッチについて読んでいて、問題が解決するかどうか疑問に思っています。

JörgW Mittag これは Phil Wadlerによる式の問題 と呼ばれていると述べていますが、問題を解決するには、

ケースごとにデータ型を定義します。既存のコードを再コンパイルすることなく、静的型の安全性を維持しながら、データ型に新しいケースと新しい関数を追加できます[...]

ソフトウェアの拡張性、つまり新しい機能を段階的に追加することが「簡単」であるかどうかに、より関心があります。この場合、私は「簡単」の直感的な定義を持っています。これには、バグの耐性があり、コードの重複はありません。


これは、本からのボブおじさんの例です

ポリモーフィック形状

public class Square implements Shape {
 private Point topLeft;
 private double side;

 public double area() {
  return side * side;
 }
}

public class Rectangle implements Shape {
 private Point topLeft;
 private double height;
 private double width;

 public double area() {
  return height * width;
 }
}

public class Circle implements Shape {
 private Point center;
 private double radius;
 public final double PI = 3.141592653589793;

 public double area() {
  return PI * radius * radius;
 }
}

手続き型

public class Square {
 public Point topLeft;
 public double side;
}

public class Rectangle {
 public Point topLeft;
 public double height;
 public double width;
}

public class Circle {
 public Point center;
 public double radius;
}

public class Geometry {
 public final double PI = 3.141592653589793;

 public double area(Object shape) throws NoSuchShapeException {
  if (shape instanceof Square) {
   Square s = (Square) shape;
   return s.side * s.side;
  } 

  else if (shape instanceof Rectangle) {
   Rectangle r = (Rectangle) shape;
   return r.height * r.width;
  } 
  else if (shape instanceof Circle) {
   Circle c = (Circle) shape;
   return PI * c.radius * c.radius;
  }
  throw new NoSuchShapeException();
 }
}
7
Jp_

これはPhil WadlerによってExpression Problemと呼ばれていますが、彼がこの用語を思いついた議論よりはるかに古いです。それを解決することは、プログラミング言語設計の「聖杯」の1つです。

非常に有名な問題の1つは、誰もが独自の定義を考え出すことです。そのため、厳密に定義せずにそれについて話すことは、フレームウォーを招待することを除いて、ほとんど意味がありません。

Phil Wadlerには、次の4つの制約がありました。

  1. 既存の種類のデータに対する新しい操作でコードを拡張します。
  2. 既存の操作で機能する新しい種類のデータでコードを拡張します。
  3. これはtrue拡張である必要があります。つまり、既存のコードを変更しないでください。
  4. 静的に型保証されている必要があります。

MonkeypatchingまたはECMAScriptスタイルのミュータブルプロトタイプを使用したRubyスタイルのミュータブルオープンクラスは、問題1および2を解決しますreallyがポイント3を解決するかどうかについては、次のように議論できます。メモリ内ですが、この変更を実行するコードは別のファイルにあります…その既存のコードの拡張または変更ですか?

つまり、2.5の問題を解決しますが、静的に型保証されていないことは明らかです。

CLOSスタイルのマルチメソッドは3を完全に解決しますが、4で失敗します。MultiJavaとタプルクラススタイルの両方のマルチメソッドが適格であると思うのですが、あまり詳しくありません。

HaskellのTypeclassesは、4つすべての制約を満たす最初のシステムでした。

マーティン・オデルスキー他別の2つの制約を追加しました。

  1. これはモジュール式である必要があります。つまり、拡張機能は、個別にデプロイされる(WordのPLTの意味で)別のモジュールにある必要があります。
  2. そして、これにはモジュラー型チェックが含まれます。

Haskell Typeclassesは実際には#6で失敗します。 Scala暗黙的な値と暗黙的な変換ですが、doは6つの問題すべてを解決します。

注:Haskellには、6つすべてを解決する他の機能がある可能性がありますが、Typeclasses以降に行われたすべての拡張機能と調査についてあまり詳しくありません。

あなたの実際の質問に答えるには:

この記事のような複数のディスパッチについて読んでいて、問題が解決するかどうか疑問に思っています

Multiple Dispatchが問題を解決するかどうかは、「解決」をどのように定義するか、および「問題」をどのように考えるかによって異なります。

9
Jörg W Mittag