1

我正在阅读 Expert F# book,我发现了这段代码

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 的值时

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

范围内,代码对于 let key 是任何类型都是非常通用的,同样的情况发生在 '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 所说,该隔离行中没有足够的信息供解释器确定类型约束。John 的建议将起作用,但原始代码正确地执行了它 - 定义变量并在同一范围内使用它,以便可以推断类型。

于 2012-04-18T03:02:34.117 回答
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与不同的具体类型(intstring)统一起来。这些统一中的任何一个都可以单独使用,但是它们彼此不兼容,并且会导致在执行第二行时将第一行插入的int值视为 a ,这显然违反了类型安全。 1string

将此与一个空列表进行对比,这确实是通用的:

let 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 回答