F# for Jupyter Notebooks

F# is an open-source and cross-platform language which excels at succinct, correct and maintainable code. F# is used for data scripting, data science, web programming and component development. It interoperates with a wide range of software libraries and tools and all .NET and C# libraries can be used directly from F#. A key characteristic of F# is that you can use it from small-scale scripting and development to large-scale software delivery.

F# is well suited for literate programming using Azure and Jupyter notebooks because declarations are ordered in a script-like way. This notebook assumes you are familiar with some programming already. However, the examples are kept simple, so if you are just learning F#, that's fine too! There are lots of excellent F# learning resources available online. The F# Cheetsheet is a quick guide.

F# is supported by the F# Software Foundation and a worldwide community of contributors. Microsoft and other companies develop professional tooling for F#. The F# Language Reference is a reference for the F# language, and the F# Guide covers general topics. F# on Azure is a technical guide for using F# in conjunction with a range of Azure services.

To learn more about how to use Jupyter notebooks, see the Jupyter documentation and the Jupyter keyboard shortcuts. You can install the F# and Jupyter tooling locally using IfSharp.

Please share your notebooks with your friends and colleagues! The #fsharp tag on twitter can be used to share with the community.

Introducing F#

Let's start with some simple arithmetic and data:


In [1]:
(12/4 + 5 + 7) * 4 - 18


Out[1]:
42

Here is a list of numbers:


In [2]:
let sampleNumbers = [ 0 .. 15 ]

sampleNumbers


Out[2]:
[0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15]

Next you use let to define a function that accepts and returns an integer. Parentheses are optional for function arguments. When needed, annotate the type of a parameter name using (argument: type). Documentation comments are added using ///.


In [3]:
/// A function to compute a sample curve
let sampleFunction (x:int) = 
    2*x*x - 5*x + 3

sampleFunction (7 + 4)


Out[3]:
190

To convert between numeric typs int, double, int64, bigint and so on, use conversion functions of the same name


In [4]:
sampleFunction (int 3.14 + int 2.79)


Out[4]:
28

F# uses indentation aware syntax (like Python). You can find out more about this in the topic F# syntax: indentation and verbosity.

Conditionals are expressed using if ... then ... else and booleans are expressed using true, false, &&, || and not:


In [5]:
if 98.0 < 100.0 then 10 else 20


Out[5]:
10

In [6]:
not false && (true || false)


Out[6]:
true

Strings, Tuples, Lists and Arrays

String data use quotes or triple-quotes:


In [7]:
let helloWorld = "Hello" + " " + "world"

helloWorld


Out[7]:
"Hello world"

A tuple combines multiple data items into one value. Here is a tuple consisting of an integer, a string, and a double-precision floating point number


In [8]:
(1, "fred", 3.1415)


Out[8]:
(1, "fred", 3.1415)

Lists are linear sequences of values of the same type. Here is a list containing all the Fridays of the first half of 2017. THis also shows you how to use some of the very extensive .NET libraries, all of which are available to F#. You can find out more about the .NET libraries in online resources such as the .NET Framework API Reference.


In [9]:
open System

let fridaysList = 
    [ for month in 1 .. 6 do
        for day in 1 .. DateTime.DaysInMonth(2017, month) do 
          let date = DateTime(2017, month, day)
          if date.DayOfWeek = DayOfWeek.Friday then 
            yield date.ToShortDateString() ]
              
fridaysList


Out[9]:
["01/06/2017"; "01/13/2017"; "01/20/2017"; "01/27/2017"; "02/03/2017";
 "02/10/2017"; "02/17/2017"; "02/24/2017"; "03/03/2017"; "03/10/2017";
 "03/17/2017"; "03/24/2017"; "03/31/2017"; "04/07/2017"; "04/14/2017";
 "04/21/2017"; "04/28/2017"; "05/05/2017"; "05/12/2017"; "05/19/2017";
 "05/26/2017"; "06/02/2017"; "06/09/2017"; "06/16/2017"; "06/23/2017";
 "06/30/2017"]

If you edit the previous bit of code yourself, you will notice that you get autocomplete assistance while editing. This gives you IDE-like editing inside your Azure Notebook.

Arrays are similar to lists but are mutable and are stored as flat data rather than linked lists:


In [10]:
let lowNumbers = [| 1 .. 200 |]

lowNumbers


Out[10]:
[|1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20; 21; 22;
  23; 24; 25; 26; 27; 28; 29; 30; 31; 32; 33; 34; 35; 36; 37; 38; 39; 40; 41; 42;
  43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56; 57; 58; 59; 60; 61; 62;
  63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74; 75; 76; 77; 78; 79; 80; 81; 82;
  83; 84; 85; 86; 87; 88; 89; 90; 91; 92; 93; 94; 95; 96; 97; 98; 99; 100; ...|]

Lists, arrays and sequences can be processed using functions. Use the pipeline operator |> and a function to process data using List.map:


In [11]:
sampleNumbers 
|> List.map (fun x -> x*x)


Out[11]:
[0; 1; 4; 9; 16; 25; 36; 49; 64; 81; 100; 121; 144; 169; 196; 225]

Pipelines can be chained together. The following pipeline computes the sum of a selection of square numbers:


In [12]:
let sumOfSelectedSquares = 
    sampleNumbers
    |> List.map (fun x -> x*x) 
    |> List.filter (fun x -> x % 3 = 0)
    |> List.sumBy (fun x -> x * x)
    
sumOfSelectedSquares


Out[12]:
79299

Both lists and arrays can use slicing notation:


In [13]:
lowNumbers.[0 .. 50]


Out[13]:
[|1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20; 21; 22;
  23; 24; 25; 26; 27; 28; 29; 30; 31; 32; 33; 34; 35; 36; 37; 38; 39; 40; 41; 42;
  43; 44; 45; 46; 47; 48; 49; 50; 51|]

Sequences

Sequences are computed on-demand and are specified using the same constructs as lists and arrays. Here is an infinite sequence which is a random walk. Use yield! to return each element of a subsequence:


In [14]:
let rnd = System.Random()

let rec randomWalk x =
    seq { yield x
          yield! randomWalk (x + rnd.NextDouble() - 0.5) }

let firstValuesOfRandomWalk = 
    randomWalk 5.0 
    |> Seq.truncate 20
    |> Seq.toList
    
firstValuesOfRandomWalk


Out[14]:
[5.0; 4.785741253; 4.571863963; 4.947138945; 4.728038037; 4.984819298;
 5.432313966; 4.954053279; 4.559970894; 4.527699268; 4.870672569; 4.679984356;
 4.999018676; 4.989069337; 4.574370387; 4.390318572; 4.391198395; 4.450470974;
 4.023696686; 3.895796695]

Type Definitions

F# is a typed language. Here you define a record type. You can learn more about F# type definitions in online resources such as F# for Fun and Profit.

The type definition uses an option value. Option values are any kind of value tagged with either 'Some' or 'None'. They are used extensively in F# code to represent the cases where many other languages would use null references.


In [15]:
type ContactCard = 
    { Name     : string
      Phone    : string
      Verified : bool
      ZipCode : string option}            
      
let sampleCard = { Name = "Alf" ; Phone = "(206) 555-0157" ; Verified = false; ZipCode=Some "90210" }

sampleCard


Out[15]:
{Name = "Alf";
 Phone = "(206) 555-0157";
 Verified = false;
 ZipCode = Some "90210";}

In [16]:
let showCard (c: ContactCard) = 
    c.Name + " Phone: " + c.Phone + (if not c.Verified then " (unverified)" else "")
    
showCard sampleCard


Out[16]:
"Alf Phone: (206) 555-0157 (unverified)"

UnitsOfMeasure

Code can be annotated with units of measure when using F# arithmetic over numeric types


In [17]:
open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames

let raceLength = 1600.0<meter>

[<Measure>]
type mile =
    /// Conversion factor mile to meter: meter is defined in SI.UnitNames
    static member asMeter = 1609.344<meter/mile>

/// Distance expressed using imperial units
let distanceToWalk  = 500.0<mile>          

// Same distanceusing metric system
let distanceToWalkInMeters = distanceToWalk * mile.asMeter   

(raceLength, distanceToWalk, distanceToWalkInMeters)


Out[17]:
(1600.0, 500.0, 804672.0)

Parallel Programming

You can use Array.Parallel.map and Async.Parallel and related functions to do parallel processing of collections:


In [18]:
let oneBigArray = [| 0 .. 100000 |]

// Do some CPU intensive computation
let rec computeSomeFunction x = 
    if x <= 2 then 1 
    else computeSomeFunction (x - 1) + computeSomeFunction (x - 2)
       
/// Do a parallel map over a large input array
let computeResults() = oneBigArray |> Array.Parallel.map (fun x -> computeSomeFunction (x % 24))

computeResults()


Out[18]:
[|1; 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144; 233; 377; 610; 987; 1597; 2584;
  4181; 6765; 10946; 17711; 28657; 1; 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144;
  233; 377; 610; 987; 1597; 2584; 4181; 6765; 10946; 17711; 28657; 1; 1; 1; 2; 3;
  5; 8; 13; 21; 34; 55; 89; 144; 233; 377; 610; 987; 1597; 2584; 4181; 6765;
  10946; 17711; 28657; 1; 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144; 233; 377;
  610; 987; 1597; 2584; 4181; 6765; 10946; 17711; 28657; 1; 1; 1; 2; ...|]

Learning More

A range of F# learning topics are available online at The F# Software Foundation

Tables

Let's start with using Util.Table to display a nicely formatted table:


In [19]:
type MyType = { FirstName: string; LastName: string; Age : int }
let records = 
    [|
        { FirstName = "Walter"; LastName = "Harp"; Age = 31 }
        { FirstName = "Jeff"; LastName = "Smith"; Age = 21 }
        { FirstName = "Ben"; LastName = "Smith"; Age = 65 }
        { FirstName = ""; LastName = "Holly"; Age= 44 }
    |]
records |> Util.Table


Out[19]:
FirstNameLastNameAge
WalterHarp31
JeffSmith21
BenSmith65
Holly44

You can also filter the table to display a subset of the data as you display it. This also sets the order of the columns:


In [20]:
Util.Table(records, [| "Age"; "LastName" |])


Out[20]:
AgeLastName
31Harp
21Smith
65Smith
44Holly

Formatting text using LaTeX

Here, we composing some LaTeX using LatexOutput and the Util.Math function provided by IfSharp.


In [21]:
"f(x)" |> Util.Math


Out[21]:
$$f(x)$$

Lovely! You have used Util.Math: string -> LatexOutput to format LaTeX. The result of your code snippet is implictly passed to the Display function and shown in your output. Next we format some operators:


In [22]:
"\int_0^\infty e^{-x^2} dx \mathrm{\ in\ \LaTeX{}}"
  |> Util.Math


Out[22]:
$$\int_0^\infty e^{-x^2} dx \mathrm{\ in\ \LaTeX{}}$$

The result of your code snippet is implictly passed to the Display function and shown in your output. The Display function is provided by IfSharp. It takes any object and attempts to display it in your notebook. By default, Display already handles many types (including LatexOutput), and later we'll see how to extend it to handle any type you need.

You can also call Display in your code, in order to display more than one output.

Charts

We can display charts, using XPlot.Plotly:


In [23]:
#load "XPlot.Plotly.Paket.fsx"
#load "XPlot.Plotly.fsx"
open XPlot.Plotly