lambdaf#refactoringgetfiles

In F#: How do I obtain a list of the filenames in a directory; expected unit have string


I'm just starting with F# so I thought I would try some simple tasks.

This lists the full paths to the xml files in a directory:

System.IO.Directory.GetFiles("c:\\tmp", "*.xml")
|> Array.iter (printfn "%s")

But I want only the file names so I tried:

System.IO.Directory.GetFiles("c:\\tmp", "*.xml")
|> Array.iter (System.IO.Path.GetFileName)
|> (printfn "%s")

This won't compile. It gives the error:

This expression was expected to have type unit
but here has type string

I searched for examples but couldn't find anything. I'm obviously missing something simple and fundamental, but what?


Solution

  • Since you are new, one thing to make it easer to fix errors is to think of statements like mathematical statements that can be built up of simpler functions but that can also be factored apart.

    So by factoring apart your problem you can get a finer grained error that becomes easier to solve.

    System.IO.Directory.GetFiles("c:\\tmp", "*.xml")
    |> Array.iter (System.IO.Path.GetFileName)
    |> (printfn "%s")
    

    is factored apart into

    let directoryArray = System.IO.Directory.GetFiles("c:\\tmp", "*.xml")
    let nameArray = Array.iter (System.IO.Path.GetFileName) directoryArray
    (printfn "%s") nameArray 
    

    now the errors should be much easier to understand

    If we look at the signature of Array.iter which is iter : ('T -> unit) -> 'T [] -> unit we see that it needs a function ('T -> unit) that takes a type and returns a unit which means return nothing, in this case printing would work, however you do not want to return nothing you want to convert the array of directories into an array of filenames. So Array.iter will not work and you need a Array function that applies a function to each item in the array and returns a new Array, Array.map does this map : ('T -> 'U) -> 'T [] -> 'U [] To better understand the Array functions and see how they work one can add a lambda function.

    Likewise with (printfn "%s"); one can add a lambda function to pass in a value.

    let directoryArray = System.IO.Directory.GetFiles("c:\\tmp", "*.xml")
    let nameArray = Array.map (fun x -> (Path.GetFileName(x))) directoryArray
    Array.iter (fun x -> printfn "%s" x) nameArray
    

    Now we can simplify the statements using |>

    System.IO.Directory.GetFiles("c:\\tmp", "*.xml")
    |> Array.map (fun x -> (Path.GetFileName(x)))
    |> Array.iter (fun x -> printfn "%s" x)
    

    and simplify again by removing the lambdas

    open System.IO
    
    Directory.GetFiles("c:\\tmp", "*.xml") 
    |> Array.map Path.GetFileName 
    |> Array.iter (printfn "%s")