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.

No comments:

Post a Comment