F#でDuck Typing

F#にはコンパイル時にシグネチャをチェックする仕組みがあるので、
静的型付け言語であるにもかかわらずDuck Typingができてしまいます。C++のような感じ。

type Taro() =
    member self.Speak () = "こんにちは!太郎です!"
    member inline self.Calc (x, y) = x + y

type Hanako() =
    member self.Speak () = "わたしは花子だ。"

// Type Extention1
type Hanako with
    member inline self.Calc (x, y) = x - y


// 静的に解決される型パラメータ(Statically Resolved Type Parameters)
// を使って制約を加えてみる
let inline Check
    (x : ^a when ^a : (member Speak : unit -> string)
            and  ^a : (member Calc : 'm * 'n -> 'o)) = x

// Member Constraint Invocation Expressions
let inline Speak x =
    (^a : (member Speak : unit -> string) (Check x))

// 複数の値はタプルにして渡すらしい ※2
let inline Calc x y z =
    (^a : (member Calc : 'm -> 'n -> 'o) (Check x), y, z)

let inline SpeakAndCalc (person : ^a) x y =
    printfn "%s" <| Speak person
    Calc person x y

let Test () =
    let taro   = SpeakAndCalc <| Taro()
    let hanako = SpeakAndCalc <| Hanako()
    taro "hello," "world"   |> printfn "%s"
    hanako 123 23           |> printfn "%d"

> Test () ;;
こんにちは!太郎です!
hello,world
わたしは花子だ。
100
val it : unit = ()
Calcメソッドはそれぞれinlineを指定することによって、ここでもさりげなくStatically Resolved Type Parametersの恩恵を受けています。型推論によって総称型と解決されます。
Taro.Calc の型は
member Calc : x: ^a * y: ^b ->  ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
となってます。


(※1)のように、あとから型を拡張したものであっても問題ないみたいです。
(※2)のところでは、型を Calc : 'm -> 'n -> 'o と制限していますが、
このように制限をかけても実際は 'm * 'n -> 'o の関数しか許可してくれません。(なんで?)
ですのでTaro, Hanako のCalcメソッドはタプルを使っています。


こうすることにより、共通のインタフェースがなくとも同じアヒルとして扱うことができます。コンパイル時チェックとはいうものの、シグネチャに違いがあればコードを書いてるそばから知らせてくれます。(VisualStudioすばらしい)


C#だとジェネリックメソッド中で、「'T型の値と'U型の値を足したい・・・!」ということができませんでしたが、F#だとinlineを指定するだけで一発解決。
でもMSDNにも注意書きがあるように、下手なinline展開はコンパイラの最適化を邪魔する恐れがあるので、利用はやや慎重に。