【问题标题】:error with f# generic follow Expert Fsharp book examplef# 泛型错误遵循 Expert Fsharp 书籍示例
【发布时间】:2012-04-18 00:06:27
【问题描述】:

我正在阅读 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 的值时

让 dict = new Dictionary>()

范围内,代码非常通用,让 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() 

对我来说(诚然我对静态类型语言有点笨)这不是真正的通用,因为当我使用它时它无法推断类型,我需要定义类型然后传递值(而不是定义它的类型基于传递的值)存在其他方式定义类型而不是如此明确..

非常感谢..非常感谢任何帮助

【问题讨论】:

  • 更新注意事项——在实际代码中很少会遇到值限制错误,推断通常可以从使用中提取出来

标签: f#


【解决方案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>>();;

【讨论】:

  • 感谢 john,但现在对我来说还不是很清楚...当我在函数中编写 dict 时,编译器不知道什么是 'key 和 'T 类型...这个函数适用于数字、字母等。实际上我需要一个: val dict : Dictionary> when '_key : 相等,如果您能提供更多详细信息或更多详细信息的链接...谢谢!!
  • @user1050817 :函数可以是通用的,值不能。 dict 使用函数的通用参数类型进行实例化。
  • @ildjarn - 值不能是通用的(例如尝试([],[]))并不完全正确,只是用于识别安全创建通用值的启发式方法非常有限。
  • @user1050817 - 关键是当你在函数中时,编译器有额外的信息,它没有。当您在 fsi 中键入 ;; 时,编译器必须进行函数调用并且无法确定正确的通用字典 - 所以您会收到错误。
【解决方案2】:

当它一起定义时,这实际上不会导致问题。也就是说,选择您发布的整个块并将其一次性发送给 FSI。我明白了:

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

但是,如果您将这些单独输入 FSI,那么正如 John Palmer 所说,该隔离行中没有足够的信息供解释器确定类型约束。 John 的建议可行,但原始代码正确执行 - 定义变量并在同一范围内使用它,以便可以推断类型。

【讨论】:

  • 比 yamen 但正是我无法理解..为什么这是有效的:"""val divideIntoEquivalenceClasses : ('T -> 'key) -> seq -> seq> when 'key : equal""" 而这不是:"""val dict : Dictionary> when '_key : equal""" 当我定义“ dict”在函数内部这也没有足够的关于类型的信息......这必须与字母、数字等一起使用......但我不知道为什么有时工作而其他时候不工作......感谢您的帮助! !...
  • 我认为您需要阅读@kvb 的答案 - F# 中的两行程序和一行程序之间存在巨大差异。编译器需要程序中进一步的信息,以确保它知道推断类型的正确约束。如果您尝试一次键入并编译(发送到 FSI)程序一行,它并不总是有足够的信息来正确推断。
【解决方案3】:

对我来说,这段代码真的很难看,即使这是安全的,它看起来更像命令式语言而不是函数式语言。

我完全同意 - 这与您的直接问题略有相干,但我认为更惯用(功能)的方法是:

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) 

这允许足够的类型推断首先避免您的问题。

【讨论】:

    【解决方案4】:

    其他答案提出的解决方法都很好。只是为了根据您的最新更新进行澄清,让我们考虑两个代码块:

    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)统一起来。这些统一中的任何一个都可以单独使用,但是它们彼此不兼容,并且会导致在执行第二行时将第一行插入的int1 视为string,这显然是违反类型安全。

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

    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])
    

    我们在每一行创建一个 new 数组,因此类型可以不同而不会破坏类型系统。 希望这有助于进一步解释限制背后的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-01
      • 2018-03-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多