Saturday, March 28, 2020

Learning F# - Step 4

In this next step of learning F#, I want to interact with the file system. Once I can read and write files, I can use a programming language for day-to-day scripting needs, which helps me get more comfortable with the language's features.

Fortunately, F# uses the .NET System.IO library for interacting with files. This fact makes it easy to translate how to work with files in C# to F#. The first thing to do is to "open" the System.IO package in your file:

open System.IO
Now that you have the library available, you can work with files.

Let's start by reading a file. The following code reads a file line by line into a sequence of lines:

let readLinesFromFile fileName =
    seq { use reader = File.OpenText fileName
        while not reader.EndOfStream
            do yield reader.ReadLine() }
This code block reads lines from file fileName and puts them into a sequence. A sequence in F# is a logical series of elements all of one type. Since the last line of an F# function is the return value, the sequence is returned from this function.

To write strings to a file as separate lines, you would use code like:

let WriteLines fileName (lines: string seq) =
    use writer = File.CreateText fileName
    for line in lines
        do writer.WriteLine line
This function takes a fileName and a sequence of lines and writes them into a file. (The parameter for the sequence of lines is contained in parentheses because the code is assigning a type of sequence of strings to the parameter lines. If this wasn't in parentheses, each word would be considered a parameter of the function.)

This is all great, but not very useful. To make sure I know how to employ file IO for a useful purpose, I created a little program to read the lines from a file, find all the lines that contain a word, and print them to the console output. Below is the program I created:

open System.IO

let readLinesFromFile filename =
    seq { use reader = File.OpenText filename
          while not reader.EndOfStream
              do yield reader.ReadLine () }

let CheckPhrase (x: string) =
    x.Contains("test")
    
[<EntryPoint>]
let firstMain argv =
    let fileLines = readLinesFromFile "testfile.txt"
    
    fileLines
        |> Seq.filter CheckPhrase
        |> printfn "Line with test -> %A"

    0

This program utilizes the functions I described above, plus one additional function. The additional function, CheckPhrase, takes a string parameter and returns true or false depending on when the input string contains the word "test." The other tidbit that is different, is the use of the Seq.filter function and the |> operator. The Seq.filter function creates a new sequence by passing one sequence (fileLines) through a "filter" function, in this case CheckPhrase. The the Seq.filter function adds an item from the first sequence into the new sequence if the passed in predicate returns true, otherwise it skips the element.

The other new piece of syntax is the pipe operator: |>. This operator "pipes" the output of the expression on the left to the expression on the right. In my program above, I am piping the fileLines variable into the Seq.filter CheckPhrase expression, whose result is piped into the printfn function.

Learning how to work with files in a new programming language is a way to gain practical experience with a new language. It also tends to expose new constructs or paradigms of the language to learn.

No comments:

Post a Comment