構造型システム(またはプロパティ ベース型システム) は、型の互換性と同等性が型の実際の構造または定義によって決定され、型の名前や宣言場所などの他の特性によって決定されない、型システムの主要なクラスです。構造システムは、型が同等であるかどうか、および型が別の型のサブタイプであるかどうかを判断するために使用されます。これは、比較が型の名前または明示的な宣言に基づいて行われる名詞的システムや、実行時にアクセスされる構造の部分のみの互換性がチェックされる ダック タイピングとは対照的です。
説明
構造型付けでは、2 番目の要素の型内の各機能に対して、最初の要素の型に対応する同一の機能が存在する場合、その要素は別の要素と互換性があると見なされます。言語によっては、機能の名前が一致する必要があるかどうかなど、詳細が異なる場合があります。この定義は対称的ではなく、サブタイプの互換性が含まれます。2 つの型は、それぞれが他の型と互換性がある場合に同一であると見なされます。
たとえば、OCaml はオブジェクト型の互換性のためにメソッドに構造型付けを使用します。Goは、インターフェイスと型の互換性を判断するためにメソッドに構造型付けを使用します。C ++ テンプレート関数は型引数に構造型付けを示します。Haxeは構造型付けを使用しますが、クラスは構造的にサブタイプ化されません。
サブタイプ多態性をサポートする言語では、サブタイプの関係がどのように定義されているかに基づいて、同様の二分法が形成されます。ある型が別の型のサブタイプとなるのは、基本型またはそのサブタイプのすべての機能が含まれている場合のみです。サブタイプには、基本型には存在しないメンバーや、より強力な不変条件などの追加機能が含まれている場合があります。
推論された多態性と非推論された多態性に対する構造的置換には違いがあります。Haskellなどの一部の言語では、予期される型が宣言されている(つまり推論されていない)場合には構造的に置換しません。たとえば、型推論を介してシグネチャベースの多態性を持つ関数に対してのみ置換します。[1]この場合、非推論型を誤ってサブタイプ化することはできませんが、暗黙的に呼び出される非推論型への明示的な変換を提供することは可能です。
構造的サブタイピングは、アドホックな型やプロトコルの作成を可能にするため、名目上のサブタイピングよりも柔軟性が高いと言えます。特に、既存の型の定義を変更することなく、その型のスーパータイプとなる型の作成が可能です。ただし、プログラマが閉じた抽象化を作成したい場合は、これが望ましくない場合があります。
構造型付けと名詞型付けの落とし穴は、異なる目的のために別々に定義された 2 つの型が、偶然に同じプロパティ (たとえば、両方とも整数のペアで構成されている) を持つ場合、たまたま同じ構造を持っているという理由だけで、型システムによって同じ型とみなされる可能性があることです。これを回避する 1 つの方法は、使用ごとに 1 つの代数データ型を作成することです。
1990年にクックらは、構造的に型付けされたオブジェクト指向言語では継承はサブタイプ化ではないことを証明した。 [2]
構造的型付けに基づいて2つの型が互換性があるかどうかを確認することは、例えば、以前にチェックされた型のスタックを維持する必要があるなど、簡単な操作ではありません。[3]
型が予想される構造と一致しない場合、エラー メッセージは名目上の型指定の場合よりも長くなります。
例
OCaml のオブジェクトは、メソッドの名前と型によって構造的に型付けされます。
オブジェクトは、名詞クラスを経由せずに 直接作成できます (直接オブジェクト)。クラスは、オブジェクトを作成するための機能としてのみ機能します。
# let x =
object
val mutable x = 5
method get_x = x
method set_x y = x <- y
end ;;
val x : < get_x : int ; set_x : int -> unit > = < obj >
ここで、OCaml対話型ランタイムは便宜上、オブジェクトの推論された型を出力します。その型(< get_x : int; set_x : int -> unit >)は、そのメソッドによってのみ定義されます。言い換えると、xの型は、名前ではなく、メソッド型「get_x : int」と「set_x : int -> unit」によって定義されます。[4]
同じメソッドとメソッドのタイプを持つ別のオブジェクトを定義するには:
# let y =
object
method get_x = 2
method set_x y = Printf . printf "%d \n " y
end ;;
val y : < get_x : int ; set_x : int -> unit > = < obj >
OCaml では、これらを同じ型とみなします。たとえば、等価演算子は同じ型の 2 つの値のみを取るように型指定されています。
# x = y ;;
- :ブール値= false
したがって、それらは同じ型でなければなりません。そうでなければ、型チェックすら行われません。これは、型の同等性が構造的であることを示しています。
メソッドを呼び出す関数を定義することができます。
# let set_to_10 a = a # set_x 10 ;;
val set_to_10 : < set_x : int -> ' a ; .. > -> ' a = < fun >
< set_x : int -> 'a; .. >最初の引数 ( )の推論された型は興味深いものです。..これは、最初の引数が、int を引数として受け取る "set_x" メソッドを持つ任意のオブジェクトである可能性があることを意味します。
したがって、オブジェクトに使用できますx。
# set_to_10 x ;;
- :単位= ()
そのメソッドとメソッド タイプを持つ別のオブジェクトを作成できます。他のメソッドは無関係です。
# let z =
object
method blahblah = 2 . 5
method set_x y = Printf . printf "%d \n " y
end ;;
val z : < blahblah : float ; set_x : int -> unit > = < obj >
「set_to_10」関数もこれに対して機能します:
# set_to_10 z ;;
10
- : 単位 = ()
これは、メソッド呼び出しなどの互換性が構造によって決まることを示しています。
「get_x」メソッドのみを持ち、他のメソッドを持たないオブジェクトの型シノニムを定義しましょう。
# タイプ simpler_obj = < get_x : int >;;
タイプsimpler_obj = < get_x : int >
オブジェクトはxこの型ではありませんが、そのメソッドのスーパーセットが含まれているxため、構造的にはこの型のサブタイプです。したがって、この型に強制変換できます。
xx
# ( x :> simpler_obj );;
- : simpler_obj = < obj > # ( x :> simpler_obj )# get_x ;; - : int = 10
ただしz、 object は構造サブタイプではないため、そうではありません。
# (z :> simpler_obj);; この式は simpler_obj = < get_x : int > 型に強制変換できません。 これは< blahblah : float; set_x : int -> unit >という型ですが、ここでは型とともに使用されています < get_x : int; .. > 最初のオブジェクト型にはget_xメソッドがありません
これは、強制の拡大に対する互換性が構造的であることを示しています。
参考文献
- ^ 「署名ベースのポリモーフィズム」。
- ^ Cook, WR; Hill, WL; Canning, PS (1990 年 1 月)。「継承はサブタイプ化ではない」。第17 回 ACM SIGPLAN-SIGACT シンポジウム「プログラミング言語の原則 - POPL '90」の議事録。カリフォルニア州サンフランシスコ。pp. 125–135。doi : 10.1145/ 96709.96721。ISBN 978-0897913430.S2CID 8225906 。
{{cite book}}: CS1 maint: location missing publisher (link) - ^ 「型の互換性: 名前と構造の等価性」。
- ^ 「オブジェクト タイプ」。
- ピアス、ベンジャミン C. (2002)。「19.3」。型とプログラミング言語。MIT プレス。ISBN 978-0-262-16209-8。
外部リンク
- WikiWikiWebの NominativeAndStructuralTyping
