tags:

views:

70

answers:

2

I am trying to figure out how to manage multiple lazy sequences from a single function in F#.

For example, in the code below, I am trying to get two sequences - one that returns all files in the directories, and one that returns a sequence of tuples of any directories that could not be accessed (for example due to permissions) with the exception.

While the below code compiles and runs, errorSeq never has any elements when used by other code, even though I know that UnauthorizedAccess exceptions have occurred.

I am using F# 2.0.

#light
open System.IO
open System

let rec allFiles errorSeq dir = 
    Seq.append
        (try 
            dir |> Directory.GetFiles
         with
            e -> Seq.append errorSeq [|(dir, e)|]
                 |> ignore
                 [||]
        )
        (try
            dir 
            |> Directory.GetDirectories 
            |> Seq.map (allFiles errorSeq)
            |> Seq.concat
         with
            e -> Seq.append errorSeq [|(dir, e)|]
                 |> ignore
                 Seq.empty
        )

[<EntryPoint>]
let main args =
    printfn "Arguments passed to function : %A" args
    let errorSeq = Seq.empty
    allFiles errorSeq args.[0] 
    |> Seq.filter (fun x -> (Path.GetExtension x).ToLowerInvariant() = ".jpg")
    |> Seq.iter Console.WriteLine
    errorSeq
    |> Seq.iter (fun x -> 
                     Console.WriteLine("Error") 
                     x)
    0
+2  A: 

Seq.append returns a new sequence, so this

Seq.append errorSeq [|(dir, e)|] 
|> ignore 
[||] 

has no effect. Perhaps you want your function to return a tuple of two sequences? Or use some kind of mutable collection to write errors as you encounter them?

Brian
Yes, using a standard `List<T>` is probably the easiest thing to do here.
Gabe
List<T> does indeed allow the errors to be reported easily. Is this fairly standard in F#, or is there a more "idiomatic" approach? I am also trying to implement the other suggestion (returning a tuple of sequences), but I haven't yet figured out how to accomplish it.
Nathan
+3  A: 

If you wanted to take a more functional approach, here's one way to do it:

let rec allFiles (errorSeq, fileSeq) dir  = 
  let files, errs =
    try 
      Seq.append (dir |> Directory.GetFiles) fileSeq, errorSeq
    with 
      e -> fileSeq, Seq.append [dir,e] errorSeq 
  let subdirs, errs =
    try
      dir |> Directory.GetDirectories, errs
    with
      e -> [||], Seq.append [dir,e] errs
  Seq.fold allFiles (errs, files) subdirs

Now we pass the sequence of errors and the sequence of files into the function each time and return new sequences created by appending to them within the function. I think that the imperative approach is a bit easier to follow in this case, though.

kvb