4

これまでのところ、F#での型推論には非常に感銘を受けていますが、実際には得られなかったものを見つけました。

//First up a simple Vect3 type
type Vect3 = { x:float; y:float; z:float } with 
  static member (/) (v1 : Vect3, s : float) = //divide by scalar, note that float
    {x=v1.x / s; y= v1.y /s; z = v1.z /s}
  static member (-) (v1 : Vect3, v2 : Vect3) = //subtract two Vect3s
    {x=v1.x - v2.x; y= v1.y - v2.y; z=v1.z - v2.z}
  //... other operators...

//this works fine
let floatDiff h (f: float -> float) x = //returns float
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

//as does this
let vectDiff h (f: float -> Vect3) x = //returns Vect3
  ((f (x + h)) - (f (x - h)))/(h * 2.0)


//I'm writing the same code twice so I try and make a generic function:
let genericDiff h (f: float -> 'a) x : 'a = //'a is constrained to a float 
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

この最後の関数を作成しようとすると、除算記号の下に青い波線が表示され、「この構成により、コードは型アノテーションで示されるよりも一般的ではなくなります。型変数'aは次のように制約されています。 'float'"と入力します。Vect3に/関数に適した演算子を提供します。なぜ私に警告するのですか?

4

3 に答える 3

6

標準の.NETジェネリックは、この種のジェネリック関数を許可するほど表現力がありません。問題は、コードが'a減算演算子をサポートするすべてのコードで機能することですが、.NETジェネリックはこの制約をキャプチャできません(インターフェイス制約はキャプチャできますが、メンバー制約はキャプチャできません)。

ただし、追加のメンバー制約を持つことができるF#inline関数と静的に解決された型パラメーターを使用できます。これらについての詳細を提供する記事を書きました。

簡単に言うと、関数をとしてマークしinline、コンパイラーに型を推測させると、次のようになります(状況が複雑になるため、型パラメーターの明示的な言及を削除しました)。

> let inline genericDiff h (f: float -> _) x = 
>   ((f (x + h)) - (f (x - h))) / (h * 2.0);;

val inline genericDiff :
  float -> (float ->  ^a) -> float -> float
    when  ^a : (static member ( - ) :  ^a *  ^a -> float)

コンパイラーは、パラメーターが静的に(インライン化中に)解決されると言う^a代わりに使用し、2つのものを取り、を返すメンバーが必要'aであるという制約を追加しました。^a-float

残念ながら、これはあなたが望むものではありません。なぜなら、あなたの-オペレーターがVect3(そしてfloatコンパイラーが推測したようにではなく)返すからです。/問題は、コンパイラが同じ型の2つの引数を持つ演算子を必要としていることだと思います(あなたの引数はVect3 * float)。別の演算子名を使用できます(たとえば/.):

let inline genericDiff2 h (f: float -> _) x = 
  ((f (x + h)) - (f (x - h))) /. (h * 2.0);;

この場合、Vect3(スカラー除算の名前を変更すると)機能しますが、簡単には機能しませんただしfloat、それを可能にするハックがある可能性があります-この回答を参照してください-慣用的なF#と私はおそらくそれの必要性を回避する方法を見つけようとします)。おそらく、要素ごとの除算を提供し、値hとして渡すことは理にかなっていVect3ますか?

于 2012-08-10T12:13:55.423 に答える
5

リテラルに一般的な数値を使用すると、次のように機能します。

let inline genericDiff h f x =
  let one = LanguagePrimitives.GenericOne
  let two = one + one
  ((f (x + h)) - (f (x - h))) / (h * two)

genericDiff 1.0 (fun y -> {x=y; y=y; z=y}) 1.0 //{x = 1.0; y = 1.0; z = 1.0;}
于 2012-08-10T14:11:30.703 に答える
1

何らかの理由で、コンパイラは除算の型シグネチャが

^a*^a -> ^b

それが大丈夫なはずなのに

^a*^c -> ^b

これは仕様の9.7によって暗示されていると思います(これは測定単位用ですが、なぜそれらが特殊なケースであるのかわかりません)。分割に目的の型シグネチャを設定できる場合は、次のように実行できます。

let inline genericDiff h (f: float -> ^a) x = 
    let inline sub a b = (^a: (static member (-):^a * ^a-> ^a) (a,b))
    let inline div a b = (^a: (static member (/):^a -> float-> ^c) (a,b))
    div (sub (f (x+h)) (f(x-h))) (h*2.0)

暗黙的に含めましsubdivが、必須ではありません。署名を明示的にするという考え方です。

^aこれがフロート以外の何物でもないという事実は、実際にはバグかもしれないと私は信じています。

于 2012-08-10T12:48:20.550 に答える