d.confused
d.confused

Reputation: 43

Nested function containing list comprehension

I can have a function like this:

let readLines (filePath:string) = [
    use sr = new StreamReader (filePath)
    while not sr.EndOfStream do
        yield sr.ReadLine ()
]

But I cannot have a function like this:

let lines (f:string) =
    let readLines (filePath:string) = [
        use sr = new StreamReader (filePath)
        while not sr.EndOfStream do
            yield sr.ReadLine ()
    ] in readLines f

I get the error:

Block following this 'let' is unfinished. Expect an expression. 

I cannot wrap my head around this error. I would expect a list of string from 'lines'.

Upvotes: 4

Views: 168

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243041

The minimal change to make your second snippet compile is to insert two (or one) more space before the closing square bracket:

let lines (f:string) =
    let readLines (filePath:string) = [
        use sr = new StreamReader (filePath)
        while not sr.EndOfStream do
            yield sr.ReadLine ()
      ] in readLines f

The reason is that the body of a let binding needs to be indented further than the let keyword - the F# compiler uses this to determine where the body expression of the function ends. This is also what the error message is trying to say "this 'let' is unfinished" almost always means that you do not have a valid expression inside let.

As @kvb mentioned in a comment, a more idiomatic way of writing this is to fully rely on the fact that the F# compiler is whitespace-sensitive and will infer where the in keywords are in correctly indented code. My personal preference is to also put [ on a new line:

let lines (f:string) =
    let readLines (filePath:string) = 
      [ use sr = new StreamReader (filePath)
        while not sr.EndOfStream do
            yield sr.ReadLine () ]
    readLines f

Upvotes: 4

Related Questions