FSharp_Jupyter_Notebooks


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


For example:


In [24]:
Heatmap(z = [[1; 20; 30]; [20; 1; 60]; [30; 60; 1]])
    |> Chart.Plot
    |> Chart.WithLayout (Layout(title = "Simple Heatmap"))


Out[24]:

In [25]:
["giraffes", 20; "orangutans", 14; "monkeys", 23]
    |> Chart.Bar
    |> Chart.WithLayout (Layout(title = "Basic Bar Chart"))
    |> Chart.WithHeight 300
    |> Chart.WithWidth 400


Out[25]:

Note that we had to #load two helper scripts in order to load the assemblies we need and to enable Display to show our charts. The first downloads and installs the required Paket packages, and the second sets up Display support.

Other Charting Libraries, e.g. Angara.Chart

You are not restricted in the choice of charting library to use. Any charting library on nuget.org can also be used. For example, below we use Angara.Chart to render charts in F# Azure Notebooks:


In [26]:
#load "Angara.Charting.Paket.fsx"
#load "Angara.Charting.fsx"

In [27]:
let x = [| for i in 0..99 -> float(i) / 10.0 |]
let y = x |> Array.map (fun x -> sin x)
let z = x |> Array.map (fun x -> cos x)

[ Angara.Charting.Plot.line(x, y); Angara.Charting.Plot.markers(x, z) ] |> Angara.Charting.Chart.ofList


Out[27]:

Computed Inline HTML

We can also format HTML and display it by producing values of a special type HtmlOutput (a record type with one field Html):


In [28]:
let html x = { Html = x }
 
html """<div style="background-color: #007fff; font-weight: bold;">Would you like some toast?</div>"""

// Exercise - try video content:
//  <video width="480" controls poster="https://archive.org/download/WebmVp8Vorbis/webmvp8.gif" >
//    <source src="https://archive.org/download/WebmVp8Vorbis/webmvp8.webm" type="video/webm">
//    <source src="https://archive.org/download/WebmVp8Vorbis/webmvp8_512kb.mp4" type="video/mp4">
//    <source src="https://archive.org/download/WebmVp8Vorbis/webmvp8.ogv" type="video/ogg">
//    Your browser doesn't support HTML5 video tag.
//  </video>"""


Out[28]:
Would you like some toast?

This type renders its content directly as HTML.

Certain sites of content are whitelisted for use from Azure Notebooks. At the time of writing these include raw.githubusercontent.com, github.com, onedrive.live.com and a range of Azure services. You can include content from these sites directly through Util.Url:


In [29]:
"https://github.com/fsharp/fsfoundation/raw/gh-pages/img/logo/fsharp256.png"
  |> Util.Url


Out[29]:

Note: Evaluation uses a REPL

Notice that we used a value from an earlier snippet in a later one. Your entire notebook is a single program running in an fsi REPL, so you can accumulate data and functions as you go. However, there's a gotcha: snippets aren't reevaluated unless you specifically request them to be. As such, if you go back and edit an earlier snippet, it can reference things that were defined in a later snippet! If you reevaluate your entire notebook, this will cause errors.

Just remember that your notebook is a single REPL instance, and you won't be surprised by this behaviour.

Custom Display Printers

The Display function can show many things in your F# Azure Notebook already, but you can extend it to display any type in any way that suits you. To do this, use IfSharp.Kernel.App.AddDisplayPrinter to add a function that accepts your type and returns a record that includes a ContentType: string (which is interpreted as a MIME Content-Type) and a Data: string, which is the content that will be interpreted by your browser as the specified Content-Type.

Often this will be "text/html" and some HTML. Here's an example:


In [30]:
open IfSharp.Kernel.App

type Person = 
    { Name: string }

AddDisplayPrinter (fun (person: Person) -> { ContentType = "text/html"; Data = "<b><i>Ahoy " + person.Name + "!</i></b>" })

After this the Person type will display in the requiested way:


In [31]:
let samplePerson = { Name = "Walter" }

samplePerson


Out[31]:
Ahoy Walter!

Using JavaScript to Display Dynamic Output

You can also use existing browser display technologies directly in your F# Azure Notebook. Let's start by loading D3:


In [32]:
@"<script src=""https://d3js.org/d3.v3.min.js""></script>" |> Util.Html |> Display


That's great! Now let's define a few F# types and functions that will let us write D3 output as structured F# data. These types are pretty simple, and don't cover everything D3 can do, but it shows how F# can interact with JavaScript directly in your notebook.


In [33]:
type D3Op = {
    Op: string
    Style: List<string * string>
    Attr: List<string * int>
    On: List<string * List<D3Op>>
}

type D3 = List<D3Op>

let d3op = {Op = ""; Style = []; Attr = []; On = []}

let mapConcat s f xs = 
    xs |> List.map f |> String.concat s

let rec D3OpPrinter (op: D3Op) =
    sprintf 
        "%s%s%s%s"
        (if op.Op.Length > 0 then
            sprintf "\t.append(\"%s\")\n" op.Op
        else
            "")
        (op.Style |> mapConcat "\t" (fun (k, v) -> sprintf ".style(\"%s\", \"%s\")\n" k v ) )
        (op.Attr |> mapConcat "\t" (fun (k, v) -> sprintf ".attr(\"%s\", %d)\n" k v))
        (op.On
            |> mapConcat "\t" (fun (k, v) ->
                sprintf 
                    ".on(\"%s\",\n\tfunction(){\n\td3.select(this)\n%s\t})\n"
                    k
                    (v |> mapConcat "" D3OpPrinter)
                ))

let D3Printer (d3: D3) =
    sprintf 
        """
<div id="viz"></div>
<script type="text/javascript">

d3.select("#viz")
%s
"""
        (d3 |> mapConcat "" D3OpPrinter)

App.AddDisplayPrinter (fun (d3: D3) ->
  { ContentType = "text/html"
    Data = D3Printer d3 }
  )

Phew, now that's all done, let's use D3 to render a circle that changes colour when you put your mouse over it. We'll do so by writing structured data.


In [34]:
[
    {d3op with 
        Op = "svg"
        Attr = [("width", 100); ("height", 100)] }
    {d3op with
        Op = "circle"
        Style = [("stroke", "grey"); ("fill", "white")]
        Attr = [("r", 40); ("cx", 50); ("cy", 50)]
        On = 
          [ ("mouseover", [{d3op with Style = [("fill", "blue")]}])
            ("mouseout", [{d3op with Style = [("fill", "white")]}]) ] }
]


Out[34]: