1

Expert F# の本を読んでいて、このコードを見つけました

open System.Collections.Generic
let divideIntoEquivalenceClasses keyf seq =
// The dictionary to hold the equivalence classes
  let dict = new Dictionary<'key,ResizeArray<'T>>()
  // Build the groupings
  seq |> Seq.iter (fun v ->
                          let key = keyf v
                          let ok,prev = dict.TryGetValue(key)
                          if ok then prev.Add(v)
                          else let prev = new ResizeArray<'T>()
                             dict.[key] <- prev
                             prev.Add(v))

 dict |> Seq.map (fun group -> group.Key, Seq.readonly group.Value)

および使用例:

> divideIntoEquivalenceClasses (fun n -> n % 3) [ 0 .. 10 ];;
val it : seq<int * seq<int>>
= seq [(0, seq [0; 3; 6; 9]); (1, seq [1; 4; 7; 10]); (2, seq [2; 5; 8])]

まず私にとって、このコードは本当に醜いです。たとえこれが安全であっても、関数型言語よりも命令型言語に似ているように見えます..特に clojure と比較して. しかし、問題はこれではありません...辞書の定義に問題があります

これを入力すると:

let dict = new Dictionary<'key,ResizeArray<'T>>();;

私はこれを得る:

pruebafs2a.fs(32,5): error FS0030: Value restriction. The value 'dict' has been inferred to have generic type
val dict : Dictionary<'_key,ResizeArray<'_T>> when '_key : equality    

Either define 'dict' as a simple data term, make it a function with explicit arguments or, if you do not intend for it to be generic, add a type annotation.

大丈夫ですか?...

本当にありがとう


質問を改善する:

わかりました、値の制限について読んでいて、この役立つ情報を見つけました

特に、関数定義と単純な不変データ式のみが自動的に一般化されます。

...わかりました..これは理由を説明しています

let dict = new Dictionary<'key,ResizeArray<'T>>();;

動作しません...そして4つの異なるテクニックを示していますが、私の意見ではエラーを解決するだけで、汎用コードを使用するための解決策ではありません:

手法 1: 値を非ジェネリックに制限する

 let empties : int list [] = Array.create 100 []

手法 3: 必要に応じてジェネリック関数にダミー引数を追加する

let empties () = Array.create 100 []
let intEmpties : int list [] = empties()   

手法 4: 必要に応じて明示的な型引数を追加する (tec 3 と同様)

let emptyLists = Seq.init 100 (fun _ -> [])
> emptyLists<int>;;
val it : seq<int list> = seq [[]; []; []; []; ...]

----- そして、本当のジェネリック コードを使用させてくれる唯一の方法 ------ テクニック 2: ジェネリック関数に明示的な引数があることを確認する

let mapFirst = List.map fst //doesn't work
let mapFirst inp = List.map fst inp

わかりました、4 つの手法のうち 3 では、これを使用する前に一般的なコードを解決する必要があります...今...本の例に戻ります...コンパイルが 'key と 'T の値を知っているとき

let dict = new Dictionary<'key,ResizeArray<'T>>()

スコープ内では、キーを任意の型にするためのコードは非常に一般的です。'T でも同じことが起こります。

最大のダミーの質問は次のとおりです。

コードを関数で囲む場合 (テクニック 3):

let empties = Array.create 100 [] //doesn't work
let empties () = Array.create 100 []
val empties : unit -> 'a list []

I need define the type before begin use it
let intEmpties : int list [] = empties() 

私にとっては (確かに私は静的型言語については少しばかげています)、これは本当のジェネリックではありません。使用時に型を推測できないためです。型を定義してから値を渡す必要があります (渡された値) 他の方法で存在し、それほど明示的でなくても型を定義できます..

どうもありがとう..本当に助けてくれてありがとう

4

4 に答える 4

1

この行

let dict = new Dictionary<'key,ResizeArray<'T>>();;

;;入力すると、コンパイラは何が何であるか'keyを認識できないため、失敗します'T。エラーメッセージに示されているように、型注釈を追加するか、コンパイラが後でそれを使用して型を推測できるようにするか、関数にする必要があります

型注釈の変更

let dict = new Dictionary<int,ResizeArray<int>>();;

後で型を使用する

let dict = new Dictionary<'key,ResizeArray<'T>>()
dict.[1] <- 2

関数を使用する

let dict() = new Dictionary<'key,ResizeArray<'T>>();;
于 2012-04-18T00:31:40.773 に答える
1

私にとって、このコードは本当に醜いです。たとえこれが安全であっても、関数型言語よりも命令型言語に似ているように見えます。

私は完全に同意します-それはあなたの直接の質問に少し接していますが、より慣用的な(機能的な)アプローチは次のようになると思います:

let divideIntoEquivalenceClasses keyf seq =
    (System.Collections.Generic.Dictionary(), seq)
    ||> Seq.fold (fun dict v ->
        let key = keyf v
        match dict.TryGetValue key with
        | false, _ -> dict.Add (key, ResizeArray(Seq.singleton v))
        | _, prev  -> prev.Add v
        dict)
    |> Seq.map (function KeyValue (k, v) -> k, Seq.readonly v) 

これにより、そもそも質問の必要性をなくすのに十分な型推論が可能になります。

于 2012-04-18T02:17:56.950 に答える
1

これは、すべて一緒に定義されている場合、実際には問題を引き起こしません。つまり、投稿したブロック全体を選択して、一度に FSI に送信します。私はこれを得る:

val divideIntoEquivalenceClasses :
  ('T -> 'key) -> seq<'T> -> seq<'key * seq<'T>> when 'key : equality

ただし、これらを個別に FSI に入力すると、John Palmer が言うように、インタープリターが型の制約を判断するには、その分離された行に十分な情報がありません。ジョンの提案は機能しますが、元のコードはそれを正しく実行しています-変数を定義し、同じスコープで使用して、型を推測できるようにします。

于 2012-04-18T03:02:34.117 に答える
0

他の回答で提案された回避策はすべて適切です。最新の更新に基づいて明確にするために、2 つのコード ブロックを考えてみましょう。

let empties = Array.create 100 []

とは対照的に:

let empties = Array.create 100 []
empties.[0] <- [1]

2 番目のケースでは、要素の型を制約する 2 行目の配列にempties : int list []an を挿入しているため、コンパイラはそれを推測できます。int list

最初のケースでコンパイラにジェネリック値を推測させたいようですempties : 'a list []が、これは不健全です。コンパイラがそれを行い、別のバッチに次の 2 行を入力するとどうなるかを考えてみましょう。

empties.[0] <- [1] // treat 'a list [] as int list []
List.iter (printfn "%s") empties.[0] // treat 'a list [] as string list []

これらの各行は、ジェネリック型パラメーター'aを異なる具象型 (intおよびstring) と統合します。これらの統合はどちらも単独では問題ありませんが、互いに互換性がなく、2 行目の実行時に1 行目で挿入されたint値を として扱うことになります。これは明らかにタイプ セーフに違反しています。 1string

これを、実際には一般的な空のリストと比較してください。

let empty = []

次に、この場合、型の安全性に影響を与えることなく、コード内のさまざまな場所にあるさまざまな型のリストとして empty を安全に扱うことができるため、コンパイラを推論します。empty : 'a list

let l1 : int list = empty
let l2 : string list = empty
let l3 = 'a' :: empty

emptiesジェネリック関数の戻り値を作る場合:

let empties() = Array.create 100 []

以前の問題のあるシナリオを試してみると、ジェネリック型を推論しても安全です。

empties().[0] <- [1]
List.iter (printfn "%s") (empties().[0])

各行で新しい配列を作成しているため、型システムを壊すことなく型を変えることができます。うまくいけば、これが制限の背後にある理由をもう少し説明するのに役立ちます.

于 2012-04-18T16:27:30.137 に答える