Equality 制約

github.com

の実装のためのメモ(宣伝)。

型制約は F# における型安全をマスターするためには必須の知識であるため、細かい仕様も見落としてはならない。

型制約は F# 言語仕様書 4.0 の 5.2 Type Constraints に記述されている。

Equality制約

Equality制約は滅多に使用しないが、かなり緩い制約になっている。

標準ライブラリでは(=)(<>)hashにEquality制約がついている。

定義

こう書く。'aにEquality 制約を指定できる。

<'a when 'a : equality>

挙動

Equality制約はほとんどの型が満たす。

なぜならEqualsメソッドのデフォルトであるSystem.Object.Equalsがリファレンスを比較するようになっているため、 全てのオブジェクトはEqualisy制約を満たす事になっている。

このコードで確認できる。

type A() = class end
let test<'a when 'a : equality> (_: 'a) = ()
do test(A())

AIEquatableを実装しないため、Equality制約を満たさないと予想するかもしれない。 実際には前述のとおり、Equality制約を満たす。

Equality制約を違反する型を定義するには、NoEqualityAttirbuteを型に付ける。

[<NoEquality; NoComparison>]
type B() = class end
do test(B()) // 型 'B' は 'NoEquality' 属性があるため、'equality' 制約をサポートしません

NoEqualityAttributeCustomEqualityAttributeはComparison制約系の属性NoComparisonAttributeCustomComparisonAttributeと組み合わせて使用する。 CustomEqualityAttributeを指定した場合はObject.Equals(obj)IEquatable<_>IStructualEquatableのどれかを実装しなければならない。

Equality制約を満たす条件は次の通り。1、2は言語仕様書に定義されている。3、4は実装を読んだ。

  1. NoEqualityAttributeを持っていない型
  2. 依存する型が全てEquality制約を満たす型
    1. レコード、判別共用体、構造体の型パラメータ、フィールドに使用される型すべて(クラスは対象外)
    2. System.Tupleの要素の型
    3. ジェネリック型のEqualityConditionalOnAttributeが指定されたパラメータ
  3. 関数FSharpFuncではない型(つまりFuncActionはEquality制約を満たす)
  4. CustomEqualityAttributeが指定された型

C# で定義された型や、.Net標準ライブラリの型はEquality制約を満たすと言える。

わかりにくい項目の補足をする。

ジェネリック型のEqualityConditionalOnAttributeが指定されたパラメータ

クラスの型パラメータにEquality制約を満たさない型を指定しても、Equality制約を満たすと判断される。

type C<'a>() = class end
do test (C<int -> int>())

型パラメータにEqualityConditionalOnAttributeを付けると、Equality制約の解決に使用されるようになる。

type C'<[<EqualityConditionalOn>]'a>() = class end
do test (C'<int -> int>()) // 型 '(int -> int)' は関数型なので、'equality' 制約をサポートしませ

CustomEqualityAttributeが指定された型

レコード、判別共用体、構造体に使う。依存する型がEquality制約を満たさなくても良くなる。

[<CustomEquality; NoComparison>]
type D<'a> = {
  Field: 'a
}
with
  override this.Equals(_) = true
  override this.GetHashCode() = 0

let f = fun () -> ()
do test({ Field = f })

EqualityConditionalOnAttributeと同時に使われた場合は、EqualityConditionalOnAttributeも考慮する必要がある。

次のような型を定義した場合は、

[<CustomEquality; NoComparison>]
type E<[<EqualityConditionalOn>]'a, 'b> = A of 'a | B of 'b
with
  override this.Equals(_) = true
  override this.GetHashCode() = 0

E<int, int -> int>という型はEquality制約を満たすが、E<int -> int, int -> int>'aが関数であるためEquality制約を満たさない。

まとめ

実装するのがツライ

リンク

Equality and Comparison Constraints in F# | Don Syme's WebLog on F# and Related Topics