静的Duck Typingでプロパティにアクセスする

F#には静的Duck Typingを行うための(非常にマイナーな)(そして難解な)仕組みがありますが、
プロパティ(setter)にアクセスする方法は少し特殊です。
マイナーすぎて多分ググってもほとんどヒットしないと思うので、一応サンプルを載せておきます。


結論から言うと、setterのシグネチャを指定する際には

when ^a : (member set_MyProperty : int -> unit)

のようにします。



これを応用して、色んな型の"Value"にアクセスするサンプルコードを書いてみました。


// 普通のプロパティを持つ普通のクラス
type MyClass(value) =
member val Value : int = value with get, set

// 変更可能なラベルを持つレコード型
type MyRecord = { mutable Value : int }

// 仕様が異なる(という想定の)クラス
type YourClass(value) =
member val StringValue : string = value with get, set

// 型拡張を変換アダプタ的に使って仕様を合わせる
type YourClass with
member this.Value with get() = int this.StringValue
and set(x:int) = this.StringValue <- string x


// Valueプロパティのgetterから値を取得する関数
let inline getValue (x:^a when ^a : (member Value : int)) =
(^a : (member Value : int) x)

// 上記のgetValueの引数xは型推論可能なので以下のようにも定義できる
let inline getValue' x =
(^a : (member Value : int) x)

// Valueプロパティのsetterを呼び出す関数(今回の主役)
let inline setValue x value =
(^a : (member set_Value : int -> unit) x, value)

let inline increment x = getValue x + 1 |> setValue x

let inline (++) x y = x + getValue y

let inline print x = getValue x |> printfn "Value = %d"


let test () =
let myClass = MyClass(9)
let myRecord = {Value = 99}
let myRef = ref 999
let yourClass = YourClass("9999")

increment myClass
increment myRecord
increment myRef
increment yourClass

print myClass
print myRecord
print myRef
print yourClass

1 ++ myClass ++ myRecord ++ myRef ++ yourClass

> test () ;;
Value = 10
Value = 100
Value = 1000
Value = 10000
val it : int = 11111


レコード型のmutableなラベルに対しても使用することができました。
また、参照セルRef<'T>にはもともとValueというメンバが定義されているのでmyRefにも適用可能です。


YourClassは、「他と若干仕様の異なるクラス」をイメージしたもので、型拡張を変換コネクタ的に使ってincrement関数が適用できる形にしています。(Adaptorパターンの簡易版)


正直あまり利用シーンは思い浮かばないですが、フレームワーク等の制約/ルールによってシグネチャが強制されるようなメンバに関しては使えるかもしれません。(継承関係には無いが、リフレクションを使って動的にメンバを特定しなきゃいけないような場面)