如何在F#中为Generic.Dictionary实现扩展方法(类型约束)?

5

可能是重复问题:
类型扩展错误

我想在F#中为System.Collections.Generic.Dictionary添加一个扩展方法。问题在于我似乎无法正确地获取类型约束。我希望以下内容能够起作用:

type Dictionary<'k, 'd when 'k : equality> with

   static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =
       let res = new Dictionary<'k, 'd> ()
       for (k, d) in xs do
           res.Add (k, d)
       res

然而,编译器抱怨我的声明与字典的声明不同。当我省略等式约束时,它不会产生特定的错误。但是它会警告它缺失了。非常感谢任何提示,最好是除了“降低警告级别”之外的提示 :-)

编辑

非常感谢KVB提供我想要的答案。

type Dictionary<'k, 'd> with

static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =

    let res = new Dictionary<'k, 'd> (EqualityComparer<'k>.Default)
    for (k, d) in xs do
        res.Add (k, d)
    res

编辑:这里有一个示例更好地解释了我的回复给RJ。它表明,当编译器能够推断出类型参数时,在实例化类型时,类型参数是可选的。它可以编译而没有警告或错误。

type System.Collections.Generic.Dictionary<'k, 'd> with
   static member test (dict:System.Collections.Generic.Dictionary<'k, 'd>) : bool =
        dict.Values |> List.ofSeq |> List.isEmpty


let test (x:System.Collections.Generic.Dictionary<'k, 'd>) =
    System.Collections.Generic.Dictionary.test x

我认为这不是重复的。引用的帖子使用普通成员,而这个问题使用静态成员。它非常有用,可以弄清楚编译器为什么坚持等式约束,但在消除警告而无需显式指定类型参数时则不太有用。(然而,这是迄今为止最有帮助的评论,非常感谢) - user1878761
在这两种情况下,问题都是如何将扩展成员添加到 Dictionary<_,_> 类型中,而另一个问题的答案解释了为什么需要调用一个接受 IEqualityCompararer<_> 的构造函数重载来避免类型约束。 - kvb
1
在您的情况下,您不能仅使用 this.Comparer,因为您正在创建静态成员,但您可以改用 EqualityComparer<_>.Default - kvb
这正是我的观点。这些问题有不同的答案,对于不熟悉EqualityComparers的人来说并不明显。无论如何,如果您愿意提交它,我将非常乐意接受它作为答案。无论如何,非常感谢! - user1878761
3个回答

5

出于某种原因,类型参数的名称必须匹配 - 对我来说这很好用。

open System.Collections.Generic
type Dictionary<'TKey, 'TValue>  with
   static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =
       let res = new Dictionary<'k, 'd> ()
       for (k, d) in xs do
           res.Add (k, d)
       res

我不知道为什么会出现这种情况(仅仅对规范进行了30秒的查看也没有找到任何线索)。
更新- 错误实际上是在Dictionary参数与方法中编写的参数相同时发生的。
type Dictionary<'a, 'b>  with
   static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =
       let res = new Dictionary<'k, 'd> ()
       for (k, d) in xs do
           res.Add (k, d)
       res

完全正常工作。这其实现在很有意义。当参数相同时,由于 new Dictionary<'k,'d> 存在一个额外的未指定约束 - 'k:equality。然而,出于某种原因,我们不能将约束放在扩展定义中(避免重复?),因此会出现错误。


当参数被称为其他名称时,您能否重现警告?我正在使用带有警告级别3的F# 3.0。在重命名类型参数之后,我得到了与之前相同的行为(即没有约束条件的警告,以及像问题中所示的约束条件下的硬错误)。请问您看到了什么? - user1878761
第二种解决方案成功地消除了警告(出于某种原因,因为约束仍然非常必要)。但是当我尝试使用新方法时,现在会出现一个新的警告:“let f() = Dictionary.ofList [1,2]”现在会抱怨它无法弄清楚'a和'b是什么,并建议我使用类似Dictionary<_,_>.ofList的东西。请问有什么解决办法吗? - user1878761
@user1878761 - 使用 Dictionary<_,_>.ofList [1,1];; 而不是 Dictionary.ofList [1,1];; 对我来说消除了错误。 - John Palmer
非常感谢您的回答。除非有其他人提出一种解决问题的方法,而不需要在实例化字典时使用显式类型参数,否则我将在明天或之后接受它。再次感谢。 - user1878761
1
请参见 https://dev59.com/OlrUa4cB1Zd3GeqPiUcB#7068107 以获取完整的解释。 - kvb

3
如果您需要各种集合中的ofSeq函数,可以考虑类似于C#集合初始化器的方法。也就是说,使它适用于任何具有Add方法的集合。这也可以避开您目前的问题。
open System.Collections.Generic
open System.Collections.Concurrent

module Dictionary =
  let inline ofSeq s = 
    let t = new ^T()
    for k, v in s do
      (^T : (member Add : ^K * ^V -> ^R) (t, k, v)) |> ignore
    t

module Collection =
  let inline ofSeq s = 
    let t = new ^T()
    for v in s do
      (^T : (member Add : ^V -> ^R) (t, v)) |> ignore
    t

open Dictionary

let xs = List.init 9 (fun i -> string i, i)
let d1 : Dictionary<_,_> = ofSeq xs
let d2 : SortedDictionary<_,_> = ofSeq xs
let d3 : SortedList<_,_> = ofSeq xs

open Collection

let ys = List.init 9 id
let c1 : ResizeArray<_> = ofSeq ys
let c2 : HashSet<_> = ofSeq ys
let c3 : ConcurrentBag<_> = ofSeq ys

有趣的是,你甚至可以将其限制为具有特定构造函数重载的集合类型。例如,如果你想使用结构相等性,可以执行以下操作:

let t = (^T : (new : IEqualityComparer< ^K > -> ^T) (HashIdentity.Structural))

哇,这个我想不到。非常感谢你。 - user1878761

0

你无法消除警告,因为你引用了一个不存在的类型,Dictionary而不是Dictionary<_,_>。如果这是你想要访问它的方式,你可以创建一个Dictionary模块。

open System.Collections.Generic

type Dictionary<'a,'b>  with
  static member ofList (xs:list<'k*'v>) =
      let res = new Dictionary<_,_> ()
      for k, v in xs do
          res.Add (k, v)
      res    

module Dictionary = 
   let ofList xs = Dictionary<_,_>.ofList xs

然后你就可以摆脱这个警告了。

Dictionary.ofList ["1",1;"2",2];;
val it : Dictionary<string,int> = dict [("1", 1); ("2", 2)]

我不确定我理解了。我一直写List.map,而不是List<_>.map。你的论点适用于列表吗? - user1878761
List是FSharp.core中的一个模块。List<_>是System.Collections.Generic.List<_>,两者完全不同。前者是不可变的单向链表,后者是可变列表,最常用于C#中。 - Robert Jeppesen
F# List模块没有泛型类型参数,只包含用于操作F# List<'T>类型的函数。 - Robert Jeppesen
我现在真的不理解。显然,我是指不可变的FSharp版本,因为Generic.List没有map方法。作为一个快速测试,你想打开MSVC看一下,当你声明“type List with static member f = 123”与“type List<'a> with static member f = 123”时,智能感知会建议什么。后者包含[]和::,而前者则没有。无论如何,我相信我可以想出一个需要一个类型参数的类型,并且不需要明确的类型参数来实例化。实际上,让我将一个附加到问题上。你能解释一下吗? - user1878761
在其他答案中检查kvb的链接。我们新建一个字典是导致警告的原因。我认为像上面那样将“ofList”放入模块是一个不错的解决方法。 - Robert Jeppesen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接