我正在阅读专家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:必要时添加显式类型参数(类似于技术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
让dict = new Dictionary&lt;'key,ResizeArray&lt;'T&gt;&gt;()
在范围内代码非常通用,因为let键是任何类型,“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()
对我来说(不可否认我是一个虚拟的静态类型语言)这不是真正的通用,因为它在我使用它时无法推断出类型,我需要定义类型然后传递值(不定义它的类型)基于传递的值)存在其他方式定义类型而不是那么明确..
非常感谢..非常感谢任何帮助
答案 0 :(得分: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>>();;
答案 1 :(得分: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)
这样就可以进行充分的类型推断,从而避免首先出现问题。
答案 2 :(得分:1)
当它一起定义时,这实际上不会引起问题。也就是说,选择您发布的整个块并一次性将其发送给FSI。我明白了:
val divideIntoEquivalenceClasses :
('T -> 'key) -> seq<'T> -> seq<'key * seq<'T>> when 'key : equality
但是,如果您将这些单独输入FSI,那么John Palmer表示在该隔离行中没有足够的信息供解释器确定类型约束。 John的建议可行,但原始代码正确执行 - 定义变量并在同一范围内使用它,以便可以推断出类型。
答案 3 :(得分:0)
其他答案提出的解决方法都很好。为了澄清您的最新更新,让我们考虑两个代码块:
let empties = Array.create 100 []
而不是:
let empties = Array.create 100 []
empties.[0] <- [1]
在第二种情况下,编译器可以推断出empties : int list []
,因为我们在第二行的数组中插入int list
,这限制了元素类型。
听起来你喜欢编译器在第一种情况下推断出通用值empties : 'a list []
,但这样做会不合理。考虑如果编译器这样做会发生什么,然后我们在另一批中输入以下两行:
empties.[0] <- [1] // treat 'a list [] as int list []
List.iter (printfn "%s") empties.[0] // treat 'a list [] as string list []
这些行中的每一行都将泛型类型参数'a
与不同的具体类型(int
和string
)统一起来。这些统一中的任何一个都可以单独处理,但它们彼此不兼容,并且当第二行是int
时,会导致将第一行插入的1
值string
视为let empty = []
执行,这显然违反了类型安全。
将此与空列表进行对比,该列表实际上是通用的:
empty : 'a list
然后在这种情况下,编译器 推断let l1 : int list = empty
let l2 : string list = empty
let l3 = 'a' :: empty
,因为可以安全地将空处理为代码中不同位置的不同类型的列表,而不会影响类型安全性:< / p>
empties
如果您使let empties() = Array.create 100 []
为通用函数的返回值:
empties().[0] <- [1]
List.iter (printfn "%s") (empties().[0])
推断泛型类型也是安全的,因为如果我们从之前尝试我们有问题的场景:
{{1}}
我们正在每行创建一个 new 数组,因此类型可以不同而不会破坏类型系统。 希望这有助于解释限制背后的原因。