F#で暗黙の型変換とか Member Constraints とか

「暗黙の型変換を明示的に呼び出す」ってなんかアレですね。

C#で書かれたライブラリを使っていると、暗黙の型変換しか用意されてないケースがあったりします。SharpDX.DrawingSizeとか。

ところがアップキャストすら暗黙には行わない世界ですから、これもやってくれません。演算子も用意されていません。

でこの記事。

Is there an equivalent to creating a C# implicit operator in F#? - Stack Overflow

let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b) x)

//その上で例えば
let a : System.Nullable<int> = (!> 1)

この呪文、「^aもしくは^bにあるop_Implicit(ry)をxに適用する」というだけです。なので

let a : System.Nullable<int> =
  System.Nullable<int>.op_Implicit 1

とかでもいいわけです。

深堀り

上の呪文は規格で言うところの「Member Constraint Invocation Expressions (§6.4.8)」というやつで、次のような構文を持つ式です。

(static-typars : (member-sig) expr)

よく似た構文に、「Member Constraints (§5.2.3)」があります。

static-typars : (member-sig)

似てるというかなんというか。

規格書から引用するとこういうコード。F#のソースから探したら、/src/fsharp/FSharp.Core/prim-types.fsiにありました。

val inline (+) : ^a -> ^b -> ^c
 when (^a or ^b) : (static member (+) : ^a * ^b -> ^c)

でそんな制約が書けるなら、§6.4.8のサンプルを改造して、こんなことができるんじゃないかと。

let inline speak (a: ^a when ^a : (member Speak: unit -> string)) =
 let x = a.Speak()
 printfn "It said: %s" x
type Duck() =
 member x.Speak() = "I'm a duck"
type Dog() =
 member x.Speak() = "I'm a dog"
let x = new Duck()
let y = new Dog()
speak x
speak y

思ったんですが無理でしたね。「このプログラムの場所の前方にある情報に基づく不確定の型のオブジェクトに対する参照です。」と怒られました。

StackOverflowで探してみると、メンバ制約呼び出し式を使えば呼べるけど、それよりinterfaceにしろよ、といった回答が。

f# - Generic type constraints and duck typing - Stack Overflow

まあMSDN Libraryにも"which is used to implement operator overloading for arithmetic operators"って書かれているので、黒魔術と思っておいた方がいいのかもしれません。

余談

素のMarkdownの貧弱さも大概ですが、はてな記法もつらいところがあります。F#のシンタックスハイライトできないからOcamlって誤魔化したり。GithubもQiitaもMarkdownだしなー。

そろそろはてなブログに移行したい。