3

次の F# を検討してください:-

type TestClass() =
    let getValFromMap m k = Map.find k m

    let addToMap map k i = map |> Map.add k i

    let mutable someMap : Map<string,int> = Map.empty

    let getValFromMapPartial key = getValFromMap someMap key
    let getValFromMapPartialAndTacit = getValFromMap someMap

    member this.AddThenGet() =
        someMap <- addToMap someMap "A" 10

        let value = getValFromMapPartial "A"
        printfn "Value from partial = %i" value   // prints out

        let value = getValFromMapPartialAndTacit "A"  // throws
        printfn "Value from partial and tacit = %i" value

[<EntryPoint>]
let main argv = 
    let test = TestClass()
    test.AddThenGet()
    0

関数getValFromMapPartialgetValFromMapPartialAndTacitは、私の考えでは同一です。F# は、それらがまったく同じ型を持っていると言います: (string -> int). それでも、それらは非常に異なる動作をし、非常に異なる方法でコンパイルされます。dotPeek を使用して逆コンパイルすると、それはgetValFromMapPartialメソッドであることがわかりますが、ctor でgetValFromMapPartialAndTacit初期化されるフィールドです。

getValFromMapPartialAndTacitF# は、最高の警告レベル (VS 2012 と 2013 の両方) でも、について文句を言いません。それでも、上記のサンプルでこの関数を呼び出すと失敗します。おそらく、someMapその可変性にもかかわらず、 の最初の空のバージョンがラップされているためです。

これら2つの機能に違いがあるのはなぜですか? 暗黙的/ポイントフリー バージョンが失敗する可能性があるという F# からの警告があるはずですか?

4

2 に答える 2

3

F# コンパイラは、パラメーターを持つfunctionsの let バインディングと、パラメーターを持たないvaluesを区別します。

  • 値の定義:のようなバインディングlet a = ...は値の定義です。その本体は、コードのさらに下にあるものを評価する前に、「それがある場所」で熱心に評価されます。

  • 関数定義:バインディング likelet f x = ...は構文上の関数定義であり、その内容は関数が呼び出されたときに評価されます。

は変更可能な変数を参照するためsomeMap、関数定義内でこの変数を使用することは、関数が呼び出されたときに変数から読み取ることを意味します。ただし、 での使用法は、宣言の時点でgetValFromMapPartialAndTacit値を読み取ります。

この動作は、値が関数であることを妨げません。let f = fun x -> ...関数を宣言するように書くこともできますが...、これも関数定義の一部になります。ただし、 と の間に定義を追加する=と、が呼び出されたときではなくfun、 の定義の時点で評価されます。f


質問のコメントでは、同じ問題がsomeMap可変参照セルで発生します。これは同じ問題です。変更可能な参照セル用に Andrew によって書き直された関数:

let getValFromMapPartialAndTacit = getValFromMap !someMap

ここでは、逆参照演算子(!)は、関数が呼び出されたときではなく、値がバインドされたときに適用されます。それは以下と同等です:

let mapRightNow = !someMap
let getValFromMapPartialAndTacit = getValFromMap mapRightNow
于 2014-11-20T17:21:20.580 に答える
2

getValFromMapPartial真の構文関数です。その署名はval getValFromMapPartial : key:string -> int. 呼び出されるたびに、 の現在の値が使用されますsomeMap。それがあなたの例で機能する理由です。someMapエントリを持っている人のバージョンにアクセスします。

一方、getValFromMapPartialAndTacitラムダ計算関数です。その署名はval getValFromMapPartialAndTacit : (string -> int)(括弧に注意してください) です。someMapラムダには、ラムダが計算された時点ののバージョンを含む、コンパイラによって生成されたクロージャがあります。そのため、あなたの例では機能しません。エントリのない元の同じバージョンのsomeMapwho に常にアクセスします。

于 2014-11-20T16:10:46.847 に答える