The following is the code listing for the program we developed in the previous section, the Hello World program of finance. Try it out for yourself and make changes to it if you like:
/// Open the System.IO namespace open System.IO /// Sample stock data, from Yahoo Finance let stockData = [ "2013-06-06,51.15,51.66,50.83,51.52,9848400,51.52"; "2013-06-05,52.57,52.68,50.91,51.36,14462900,51.36"; "2013-06-04,53.74,53.75,52.22,52.59,10614700,52.59"; "2013-06-03,53.86,53.89,52.40,53.41,13127900,53.41"; "2013-05-31,54.70,54.91,53.99,54.10,12809700,54.10"; "2013-05-30,55.01,55.69,54.96,55.10,8751200,55.10"; "2013-05-29,55.15,55.40,54.53,55.05,8693700,55.05" ] /// Split row on commas let splitCommas (l:string) = l.Split(',') /// Get the row with lowest trading volume let lowestVolume = stockData |> List.map splitCommas |> List.minBy (fun x -> (int x.[5]))
The pipe operator makes the logic of the program very straightforward. The program takes the list stockData
, splits for commas, then selects specific columns and applies a mathematical operator. Then, it selects the maximum value of these calculations and finally returns the first column of the row fulfilling the minBy
criteria. You can think of it as building blocks, where each piece is a standalone function on its own. Combining many functions into powerful programs is the philosophy behind functional programming.
Let's extend the preceding program to read data from a file instead. Since having it explicitly declared in the code is not that useful in the long run, as data tends to change. During this extension, we will also introduce exceptions and how they are used in .NET.
We start by writing a simple function to read all the contents from a file, where its path is passed as an argument. The argument is of type string, as you can see in the function header using the type annotation. Annotations are used either when the compiler can't figure out the type on its own, or when you as a programmer want to clarify the type used or enforce a certain type.
/// Read a file into a string array let openFile (name : string) = try let content = File.ReadAllLines(name) content |> Array.toList with | :? System.IO.FileNotFoundException as e -> printfn "Exception! %s " e.Message; ["empty"]
The function will catch FileNotFoundException
if the file is not found. There is also a new operator (:?
) before the exception type. This is a type test operator, which returns true if the value matches the specified type, otherwise, returns false.
Let's change the preceding code to use the content loaded from the file instead of the pre-coded stock prices.
/// Get the row with lowest trading volume, from file let lowestVolume = openFile filePath |> List.map splitCommas |> Seq.skip 1 |> Seq.minBy (fun x -> (int x.[5]))
There are some minor changes needed to the code to make it able to handle the input from the Comma-Separated Values (CSV) file. As with the input to the pipes, we use the result from the call to the openFile
function. Then, we split for commas as before. It was necessary to have a way to skip the first line; this is easy to do in F#, and you just insert a Seq.skip n
, where n is the number of elements in the sequence to skip.
printfn "Lowest volume, found in row: %A" lowestVolume
Here, we simply use printfn
formatted with %A
, which will just take anything and format for output (very convenient).
Let's look at one more example of this useful string formatter:
> printfn "This works for lists too: %A" [1..5];; This works for lists too: [1; 2; 3; 4; 5] val it : unit = ()
Let's look at the code for the entire program, which we looked at in the previous section.
/// Open the System.IO namespace open System.IO let filePath = @" table.csv" /// Split row on commas let splitCommas (l:string) = l.Split(',') /// Read a file into a string array let openFile (name : string) = try let content = File.ReadAllLines(name) content |> Array.toList with | :? System.IO.FileNotFoundException as e -> printfn "Exception! %s " e.Message; ["empty"] /// Get the row with lowest trading volume, from file let lowestVolume = openFile filePath |> List.map splitCommas |> Seq.skip 1 |> Seq.minBy (fun x -> (int x.[5])) /// Use printfn with generic formatter, %A printfn "Lowest volume, found in row: %A" lowestVolume