Еще один вопрос ограничения ценностей
В следующем коде 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<' _ a>) когда "_b:> seq<'_ a> и" _c:> seq<'_ a> Либо сделайте явные аргументы для generateUnique, либо, если вы не собираетесь использовать его как универсальный, добавьте аннотацию типа.
Добавление явного параметра типа (let generateUnique<'T> = ...
) устраняет ошибку, но теперь возвращает разные результаты.
Вывод без параметра типа (желаемое / правильное поведение):
["mscorlib"; "TEST"]
["FSharp.Core"; "System"]
["System.Core"; "System.Security"]
[]
[]
И с:
["mscorlib"; "TEST"]
["mscorlib"; "TEST"]
["mscorlib"; "TEST"]
["mscorlib"; "TEST"]
["mscorlib"; "TEST"]
Почему поведение меняется? Как я могу сделать функцию универсальной и добиться желаемого поведения?
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
Я не думаю, что ваше определение является совершенно правильным: мне кажется, что f
должен быть синтаксическим аргументом generateUnique
(то есть я не верю, что имеет смысл использовать тот же HashSet
для разных f
с). Поэтому простое исправление:
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