有没有一种安全的方法将Collection转换为F#中的序列?

时间:2012-11-26 20:56:39

标签: .net f# functional-programming

下午好,全部!

所以我一直在讨论将.NET Collection转换为功能数据结构的方法。我能得到的最好的就是先将它投射到seq,然后投射到我想要的任何东西。

问题是这似乎打破了类型推断,这显然是不安全的。

示例:

let a = new System.DirectoryServices.DirectorySearcher("<query>") in
let entries = a.FindAll ()
let entries_list = 
     let (entries_seq : seq<obj>) = Seq.cast entries_list in
     Seq.toList entries_Seq
in
entries_list (* list of AD objects found from query, has type obj *)

为了对entries_list做任何有用的事情,我必须这样做:

entries_list :?> SearchResult

尝试将其概括为seq&lt;'a&gt;失败,因为编译器仍然要求我静态地键入其枚举器(这是有意义的)。

有没有办法避免这种情况?我开始认为这是以功能方式使用.NET数据结构的限制。

很抱歉,如果这是一个新手问题;我对F#和一般的函数式编程都很环保(我喜欢它!)。干杯!

  • 卡洛斯。

2 个答案:

答案 0 :(得分:11)

正如Daniel所说,您通常不需要使用Seq.cast,因为大多数集合已经实现了通用的seq<'t>接口。但是,有几种.NET集合类型是在.NET 2.0中引入泛型之前构建的,它只实现了非泛型IEnumerable接口。 F#编译器实际上在for循环中有一些称为“可枚举提取”的特殊逻辑,以便更容易地对付这些类型的集合。因此,如果您只处理其中一种集合类型(例如,您经常使用DirectoryServices.SearchResultCollections),那么简单地创建一个简单的辅助函数可能是有意义的:

let typedSearchResults (s:SearchResultCollection) =
    seq { for result in s -> result }

然后您可以使用Seq.cast代替此特定集合类型。

如果您在同一个项目中使用了许多不同的旧式集合,那么您可以使用一些花哨的F#功能来制作通用的Seq.cast替代方案:

module Seq =
    let inline inferCast s = 
        // constrain ^t to have an Item indexed property (which we don't actually invoke)
        let _ = fun x -> (^t : (member Item : int -> ^v with get) (x, 0))
        let e = (^t : (member GetEnumerator : unit -> ^e) s)
        seq { while (^e : (member MoveNext : unit -> bool) e) do
                yield (^e : (member Current : obj) e) :?> ^v }

现在您可以使用Seq.inferCast代替Seq.cast,并为您推断出正确的项目类型。不过,这可能在你的情况下有点过分了。

答案 1 :(得分:3)

大多数.NET集合实现IEnumerable<T>(在F#中别名为seq<'T>,但偶尔会遇到只实现非泛型接口IEnumerable的.NET集合。 SearchResultCollection就是这样一种类型。您可以使用Seq.cast将这些集合转换为seq<'T>,从而可以将它们与Seq模块中的函数一起使用。

open System.DirectoryServices

use searcher = new DirectorySearcher("<query>")
let entries = searcher.FindAll() |> Seq.cast<SearchResult>
let entries_list = Seq.toList entries