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?

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Kevin Whitefoot
  • 414
  • 3
  • 15
  • 1
    System.IO.Path.GetFileName returns a string. So instead of iter need to use the map. – FoggyFinder Feb 18 '16 at 12:25
  • 1
    System.IO.Directory.GetFiles("c:\\tmp", "*.xml") |> Array.map System.IO.Path.GetFileName |> Array.iter (printfn "%s") – FoggyFinder Feb 18 '16 at 12:26
  • This might be useful for you: http://bartoszmilewski.com/2011/01/05/using-f-sequences-and-pipelines/ – Istvan Feb 18 '16 at 12:26
  • @ 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. – Kevin Whitefoot Feb 18 '16 at 12:38
  • since you are familiar with VB.NET for starters, you can remember: For Each - iter; Select - map. – FoggyFinder Feb 18 '16 at 15:54

1 Answers1

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") 
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • 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. – Kevin Whitefoot Feb 18 '16 at 12:44
  • 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. – Guy Coder Feb 18 '16 at 12:49
  • 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? – Kevin Whitefoot Feb 18 '16 at 12:56
  • 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. – Kevin Whitefoot Feb 18 '16 at 13:02
  • Nothing to be sorry about, it's about learning. Is the answer clear and has enough detail? – Guy Coder Feb 18 '16 at 13:04
  • @FoggyFinder 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. – Guy Coder Feb 18 '16 at 16:11
  • I agree, also sometimes notice a similar. – FoggyFinder Feb 18 '16 at 16:41