Monday, April 13, 2020

F# Sequence

When I was learning how to work with files, I was introduced to the seq keyword. This keyword is used to create a Sequence in F#. Sequences are powerful because they basically implement the IEnumerable<T> data structure in .NET.

IEnumerable<T> in .NET is an interface that exposes an enumerator of type T. This means that any data structure that implements IEnumerable<T> can be used in a loop to process the contents of the data structure. This construct is a powerful tool used for processing collections of things in the .NET space. Sequences are the manifestation of IEnumerable<T> in F#. Since Sequences are the generic implementation for collection data structures in F#, I need to understand them better.

When I was introduced to Sequences when working with files, I didn't fully understand how to iterate over the list and get direct access to the items in the collection. I was using the Seq.filter function to "iterate" over the list. Seq.filter will pass over each item in the Sequence, but it creates a new Sequence containing the items filtered by the function that is passed into it. I had incorrectly assumed it would output the filtered item. It does not. The filter function creates a new Sequence!

One approach that would have worked for my purposes was using a for loop. This construct loops over every item in a collection and executes the code in the containing block for every item in the collection. for loops work very well, but they generally aren't considered a good approach for a functional programming mindset (see the note below).

Many functional programming languages have a map function (or an equivalent). This function executes the code passed into it as a function on each element in the collection. F# has the Seq.iter function to do this iteration. This function iterates over each element in the Sequence, applying the function that was passed in to the element. This is what I was missing!

Now that I can simply iterate over a collection, I can easily read a file and print each line to the console. The following code does it:

open System
open System.IO

let readLinesFromFile fileName =
    seq { use reader = File.OpenText fileName
        while not reader.EndOfStream
            do yield reader.ReadLine() }
                
[<EntryPoint>]
let main argv =
    let lines = readLinesFromFile "test.txt"

    lines
        |> Seq.iter (fun x -> printfn "%s" x)
        
    0 // return an integer exit code

I feel better about my understanding of Sequences and how to use them in basic scenarios. It isn't everything I need to know, but I feel better equipped to use F#.

NOTE: Many functional programming languages don't implement looping constructs in the language definition. Those languages accomplish the same thing with a "map" function to iterate over a collection and sophisticated function passing to process the contents of the collection. Recursion is also common to avoid looping constructs. These approaches are considered more efficient in functional programming.