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 Extention ※1 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 () ;;Calcメソッドはそれぞれinlineを指定することによって、ここでもさりげなくStatically Resolved Type Parametersの恩恵を受けています。型推論によって総称型と解決されます。
こんにちは!太郎です!
hello,world
わたしは花子だ。
100
val it : unit = ()
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展開はコンパイラの最適化を邪魔する恐れがあるので、利用はやや慎重に。