【问题标题】:Yet Another Value Restriction Question又一个价值限制问题
【发布时间】:2011-09-30 18:13:26
【问题描述】:

在以下代码中,Seq.generateUnique 被限制为 ((Assembly -> seq<Assembly>) -> seq<Assembly> -> seq<Assembly>) 类型。

open System
open System.Collections.Generic
open System.Reflection

module Seq =
  let generateUnique =
    let known = HashSet()
    fun f initial ->
      let rec loop items = 
        seq {
          let cachedSeq = items |> Seq.filter known.Add |> Seq.cache
          if not (cachedSeq |> Seq.isEmpty) then
            yield! cachedSeq
            yield! loop (cachedSeq |> Seq.collect f)
        }
      loop initial

let discoverAssemblies() =
  AppDomain.CurrentDomain.GetAssemblies() :> seq<_>
  |> Seq.generateUnique (fun asm -> asm.GetReferencedAssemblies() |> Seq.map Assembly.Load)

let test() = printfn "%A" (discoverAssemblies() |> Seq.truncate 2 |> Seq.map (fun asm -> asm.GetName().Name) |> Seq.toList)
for _ in 1 .. 5 do test()
System.Console.Read() |> ignore

我希望它是通用的,但是将其放入文件中而不使用它会产生值限制错误:

值限制。价值 'generateUnique' 已被推断为 有泛型类型 val generateUnique : (('_a -> '_b) -> '_c -> seq) 当 '_b :> seq 和 '_c :> seq “生成唯一”的参数显式 或者,如果您不打算这样做 泛型,添加类型注释。

添加显式类型参数 (let generateUnique&lt;'T&gt; = ...) 可消除错误,但现在返回不同的结果。

没有类型参数的输出(期望/正确的行为):

["mscorlib"; "TEST"]
["FSharp.Core"; "System"]
["System.Core"; "System.Security"]
[]
[]

还有:

["mscorlib"; "TEST"]
["mscorlib"; "TEST"]
["mscorlib"; "TEST"]
["mscorlib"; "TEST"]
["mscorlib"; "TEST"]

为什么行为会改变?我怎样才能使函数泛型实现所需的行为?

【问题讨论】:

  • @Huusom:这里还有一些事情要做。这就像 distinct + 递归 collect + 记忆化,它们之间有微妙的相互依赖关系。

标签: f# type-inference value-restriction


【解决方案1】:

我认为您的定义不太正确:在我看来 f 需要成为 generateUnique 的句法参数(也就是说,我认为使用相同的参数没有意义HashSet 用于不同的 fs)。因此,一个简单的解决方法是:

let generateUnique f =    
    let known = HashSet()    
    fun initial ->      
        let rec loop items =         
            seq {          
                let cachedSeq = items |> Seq.filter known.Add |> Seq.cache          
                if not (cachedSeq |> Seq.isEmpty) then            
                    yield! cachedSeq            
                    yield! loop (cachedSeq |> Seq.collect f)        
            }      
        loop initial

【讨论】:

  • 这会产生后者,带有和不带有类型参数的不正确输出。我希望f 是不确定的,因此我将它传递给内部函数(不确定这是否是一个很好的理由)。
【解决方案2】:

generateUnique 很像标准的memoize 模式:它应该用于从普通函数计算记忆函数,而不是自己进行实际缓存。

@kvb 关于此转变所需的定义更改是正确的,但是您需要更改discoverAssemblies 的定义,如下所示:

let discoverAssemblies =
  //"memoize"
  let generator = Seq.generateUnique (fun (asm:Assembly) -> asm.GetReferencedAssemblies() |> Seq.map Assembly.Load)

  fun () ->
      AppDomain.CurrentDomain.GetAssemblies() :> seq<_>
      |> generator

【讨论】:

  • 这行得通,并且实际上使用显式类型参数修复了版本,无需更改 kvb。
  • 很酷,但我认为您仍然应该使用该函数的@kvbs 版本,因为它“记忆”f(每个f 都有一个新的HashSet),而我认为该版本使用显式类型参数,每种类型只给出一个HashSet
猜你喜欢
  • 2014-11-12
  • 2011-09-11
  • 1970-01-01
  • 2012-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多