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

1

6

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?

Lubalubba answered 18/2, 2016 at 12:18 Comment(5)
System.IO.Path.GetFileName returns a string. So instead of iter need to use the map.Kerrykersey
System.IO.Directory.GetFiles("c:\\tmp", "*.xml") |> Array.map System.IO.Path.GetFileName |> Array.iter (printfn "%s")Kerrykersey
This might be useful for you: bartoszmilewski.com/2011/01/05/using-f-sequences-and-pipelinesBacteriolysis
@ Foggy Finder Thanks. That's perfect. Looks like I need to try harder to understand the differences between map and iter. And thanks for the link; it looks like a very useful article.Lubalubba
since you are familiar with VB.NET for starters, you can remember: For Each - iter; Select - map.Kerrykersey
L
19

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") 
Lael answered 18/2, 2016 at 12:37 Comment(7)
I'm new to F# not to programming or logic in general. My problem was not understanding the difference between iter and map. I still don't see it; the correct version still gives a lambda to both, syntactically they seem to be the same. Thanks for the suggestion about decomposing the pipeline though, I hadn't realized that I would get better error messages that way. By the way it seems that the lambdas do not need to be spelled out; see Foggy Finder's comment.Lubalubba
I write the answers at a lower level because other people may read this and they may not have that knowledge. SO is about helping everyone including you. Think about all of the answers you read that sometimes leave out some detail that is simple for some but necessary for others.Lael
About the error message. I don't get the message that you showed. I'm using VS2015, my error messages are the ones shown in the IDE, I get the same in F# Interactive. Did yours come from somewhere else?Lubalubba
Sorry, looks like I misunderstood this: "now the errors should be much easier to understand". I assumed that the box underneath was output from the compiler.Lubalubba
Nothing to be sorry about, it's about learning. Is the answer clear and has enough detail?Lael
@Kerrykersey Thanks, I don't know why but the ones I think don't deserve any points get a lot of them and the ones I work the hardest at get next to no votes.Lael
I agree, also sometimes notice a similar.Kerrykersey

© 2022 - 2024 — McMap. All rights reserved.