Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a safe way of converting a Collection to a sequence in F#?

Good afternoon, all!

So I've been playing around with ways of casting a .NET Collection to a functional data structure. The best I've been able to get is to cast it to a seq first and to whatever I want after that.

The problem is that this seems to break type inference, which is obviously unsafe.

Example:

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 *)

In order to do anything useful with entries_list, I'd have to do:

entries_list :?> SearchResult

Trying to generalise it to a seq<'a> fails, since the compiler still requires that I statically type its enumerator (which makes sense).

Is there any way of avoiding this? I'm starting to think this is a limitation of using .NET data structures in a functional manner.

Sorry if this is a novice question; I'm green to F# and functional programming in general (and am loving it!). Cheers!

  • Carlos.
like image 769
Carlos Nunez Avatar asked Nov 26 '12 20:11

Carlos Nunez


2 Answers

As Daniel says, you typically shouldn't need to use Seq.cast because most collections will already implement the generic seq<'t> interface. However, there are several .NET collection types that were built before the introduction of generics in .NET 2.0 which only implement the non-generic IEnumerable interface. The F# compiler actually has some special logic in for loops called "enumerable extraction" to make working against these kinds of collections a bit easier. Therefore, if you're only dealing with one of these collection types (e.g. you're working with DirectoryServices.SearchResultCollections a lot), then it probably makes sense to simply create a simple helper function:

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

which you can then use instead of Seq.cast for this particular collection type.

If you're using lots of different old-style collections in the same project, then you can use some fancy F# features to make a generic Seq.cast alternative:

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 }

Now you can use Seq.inferCast instead of Seq.cast, and the correct item type will be inferred for you. This is probably overkill in your case, though.

like image 109
kvb Avatar answered Nov 19 '22 00:11

kvb


Most .NET collections implement IEnumerable<T> (aliased as seq<'T> in F#) but occasionally you'll run across one that only implements the non-generic interface IEnumerable. SearchResultCollection is one such type. You can use Seq.cast to convert those collections to seq<'T>, making it possible to use them with functions in the Seq module.

open System.DirectoryServices

use searcher = new DirectorySearcher("<query>")
let entries = searcher.FindAll() |> Seq.cast<SearchResult>
let entries_list = Seq.toList entries
like image 28
Daniel Avatar answered Nov 19 '22 02:11

Daniel