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.
Let's start with some simple arithmetic and data:
In [1]:
(12/4 + 5 + 7) * 4 - 18
Out[1]:
Here is a list of numbers:
In [2]:
let sampleNumbers = [ 0 .. 15 ]
sampleNumbers
Out[2]:
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]:
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]:
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]:
In [6]:
not false && (true || false)
Out[6]:
In [7]:
let helloWorld = "Hello" + " " + "world"
helloWorld
Out[7]:
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]:
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]:
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]:
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]:
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]:
Both lists and arrays can use slicing notation:
In [13]:
lowNumbers.[0 .. 50]
Out[13]:
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]:
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]:
In [16]:
let showCard (c: ContactCard) =
c.Name + " Phone: " + c.Phone + (if not c.Verified then " (unverified)" else "")
showCard sampleCard
Out[16]:
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]:
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]:
A range of F# learning topics are available online at The F# Software Foundation
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]:
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]:
In [21]:
"f(x)" |> Util.Math
Out[21]:
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]:
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.
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.
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]:
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]:
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]:
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.
Display
PrintersThe 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]:
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]: