2011-07-07 38 views
2

在下面的代碼中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 

我想它是通用的,但把它變成一個文件,除了它的使用產生的值限制錯誤:

Value restriction. The value 'generateUnique' has been inferred to have generic type val generateUnique : (('_a -> '_b) -> '_c -> seq<'_a>) when '_b :> seq<'_a> and '_c :> seq<'_a> Either make the arguments to 'generateUnique' explicit or, if you do not intend for it to be generic, add a type annotation.

添加一個顯式類型參數(let generateUnique<'T> = ...)消除錯誤,但現在它返回不同的結果。

輸出無類型參數(所需的/正確的行爲):

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

而且具有:

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

爲什麼會發生行爲改變?我怎麼能使功能通用達到預期的行爲?

+0

爲什麼不使用Seq.distinctBy? – Huusom

+0

@Huusom:這裏還有更多。這就像'獨特'+遞歸'收集'+記憶,它們之間有微妙的相互依賴關係。 – Daniel

回答

3

generateUnique很像標準memoize模式:它應該被用來從正常功能計算memoized功能,而不是做實際的緩存本身。

@kvb說得對,在這種轉變所需要的定義的變化,但你需要改變的discoverAssemblies的定義如下:

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

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

這是有效的,並且實際上用明確的類型參數修正了版本,否定了對kvb變化的需要。 – Daniel

+0

很酷,但我認爲你應該仍然使用@kvbs版本的函數,因爲它「記憶」f(每個'f'都有一個新的'HashSet'),而我認爲具有顯式類型參數的版本只給出一個'HashSet'每種類型! –

+0

啊,你說得對。謝謝! – Daniel

3

我不認爲你的定義是相當正確的:在我看來是f需要一個語法參數generateUnique(也就是,我不認爲它是有道理的使用相同的HashSet的不同f s)。因此,一個簡單的解決方法是:

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 
+0

這會產生後者,帶有或不帶有類型參數的輸出不正確。我期望'f'是非確定性的,因此我將它傳遞給內部函數(不確定這是否是一個好理由)。 – Daniel