Equality 制約
の実装のためのメモ(宣伝)。
型制約は 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())
AはIEquatableを実装しないため、Equality制約を満たさないと予想するかもしれない。
実際には前述のとおり、Equality制約を満たす。
Equality制約を違反する型を定義するには、NoEqualityAttirbuteを型に付ける。
[<NoEquality; NoComparison>] type B() = class end do test(B()) // 型 'B' は 'NoEquality' 属性があるため、'equality' 制約をサポートしません
NoEqualityAttribute、CustomEqualityAttributeはComparison制約系の属性NoComparisonAttribute、CustomComparisonAttributeと組み合わせて使用する。
CustomEqualityAttributeを指定した場合はObject.Equals(obj)、IEquatable<_>、IStructualEquatableのどれかを実装しなければならない。
Equality制約を満たす条件は次の通り。1、2は言語仕様書に定義されている。3、4は実装を読んだ。
NoEqualityAttributeを持っていない型- 依存する型が全てEquality制約を満たす型
- レコード、判別共用体、構造体の型パラメータ、フィールドに使用される型すべて(クラスは対象外)
System.Tupleの要素の型- ジェネリック型の
EqualityConditionalOnAttributeが指定されたパラメータ
- 関数
FSharpFuncではない型(つまりFunc、ActionはEquality制約を満たす) 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