{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "[this doc on github](https://github.com/dotnet/interactive/tree/main/samples/notebooks/fsharp)\n", "\n", "# Introduction to F# #\n", "\n", "F# is an [open-source, cross-platform functional programming language](http://aka.ms/fsharphome) for .NET.\n", "\n", "F# has features and idioms to support functional programming while also offering clean interop with C# and existing .NET codebases and systems. It can use anything in the [NuGet](https://www.nuget.org/) ecosystem, as this notebook will also demonstrate." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## F# basics\n", "\n", "Let's start with some simple arithmetic:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "(12/4 + 5 + 7) * 4 - 18" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arithmetic is nice, but there's so much more you can do. Here's how you can generate some data using the `[start .. end]` range syntax:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let numbers = [0 .. 10]\n", "numbers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use slices with the `.[start .. end]` syntax to slice a subset of the data you just generated:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "// Take the numbers from 2nd index to the 5th\n", "numbers.[2..5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And you can use indexer syntax (`.[index]`) to access a single value:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "numbers.[3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions in F# #\n", "\n", "Since F# is a functional language, functions are one of the first things to learn. You do that with the `let` keyword. F#, like Python, uses indentation to define code blocks:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let sampleFunction x =\n", " 2*x*x - 5*x + 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "F# uses type inference to figure out types for you. But if needed, you can specify types explicitly:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let sampleFunction' (x: int) =\n", " 2*x*x - 5*x + 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When calling F# functions, parentheses are optional:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "sampleFunction 5" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "sampleFunction' 12" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can define and compose F# functions easily:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let negate x = -x\n", "let square x = x * x\n", "let print x = printfn \"The number is %d\" x\n", "\n", "let squareNegateThenPrint x =\n", " print (negate (square x))\n", " \n", "squareNegateThenPrint 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The pipeline operator `|>` is used extensively in F# code to chain functions and arguments together. It helps readability when building functional \"pipelines\":" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "// Redefine the function with pipelines\n", "let squareNegateThenPrint x =\n", " x\n", " |> square\n", " |> negate\n", " |> print\n", "\n", "squareNegateThenPrint 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Strings, tuples, lists, and arrays\n", "\n", "Strings in F# use `\"` quotations. You can concatenate them with the `+` operator:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let s1 = \"Hello\"\n", "let s2 = \"World\"\n", "\n", "s1 + \", \" + s2 + \"!\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use triple-quoted strings (`\"\"\"`) if you want to have a string that contains quotes:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "\"\"\"A triple-quoted string can contain quotes \"like this\" anywhere within it\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tuples\n", "\n", "Tuples are simple combinations of data items into a single value. The following defines a tuple of an integer, string, and double:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "(1, \"fred\", Math.PI)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also create `struct` tuples when you have performance-sensitive environments:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "struct (1, Math.PI)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Lists\n", "\n", "Lists are linear sequences of values of the same type. In fact, you've already seen them above when we generated some numbers!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "[0 .. 10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use list comprehensions to generate more advanced data programmatically:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let thisYear = DateTime.Now.Year\n", "\n", "let fridays =\n", " [\n", " for month in 1 .. 10 do\n", " for day in 1 .. DateTime.DaysInMonth(thisYear, month) do\n", " let date = DateTime(thisYear, month, day)\n", " if date.DayOfWeek = DayOfWeek.Friday then\n", " date.ToShortDateString()\n", " ]\n", "\n", "// Get the first five fridays of this year\n", "fridays\n", "|> List.take 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since you can slice lists, the first five fridays could also be done like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "fridays.[..4]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Arrays\n", "\n", "Arrays are very similar to lists. A key difference is that array internals are mutable. They also have better performance characteristics than lists." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let firstTwoHundred = [| 1 .. 200 |]\n", "firstTwoHundred.[197..]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Processing lists and arrays is typically done by built-in and custom functions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "// Filter the previous list of numbers and sum their squares.\n", "firstTwoHundred\n", "|> Array.filter (fun x -> x % 3 = 0)\n", "|> Array.sumBy (fun x -> x * x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Types\n", "\n", "Although F# is succinct, it actually uses static typing! Types are central to F# programming, especially when you want to model more complicated data to manipulate later in a program." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Records\n", "\n", "Record types are used to combine different kinds of data into an aggregate. They cannot be `null` and come with default comparison and equality." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "type ContactCard =\n", " { Name: string\n", " Phone: string\n", " ZipCode: string }\n", "\n", "// Create a new record\n", "{ Name = \"Alf\"; Phone = \"(555) 555-5555\"; ZipCode = \"90210\" }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook environment, records print with a table-like output by default.\n", "\n", "You can access record labels with `.`-notation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let alf = { Name = \"Alf\"; Phone = \"(555) 555-5555\"; ZipCode = \"90210\" }\n", "alf.Phone" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Records are comparable and equatable:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "// Create another record\n", "let ralph = { Name = \"Ralph\"; Phone = \"(123) 456-7890\"; ZipCode = \"90210\" }\n", "\n", "// Check if they're equal\n", "alf = ralph" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You'll find yourself writing functions that operate on records all the time:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let showContactCard contact =\n", " contact.Name + \" - Phone: \" + contact.Phone + \", Zip: \" + contact.ZipCode\n", " \n", "showContactCard alf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Discriminated Unions\n", "\n", "Discriminated Unions (often called DUs) provide support for values that can be one of a number of named cases. These cases can be completely different from one another.\n", "\n", "In the following example, we combine records with a discriminated union:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "type Shape =\n", " | Rectangle of width: float * length: float\n", " | Circle of radius: float\n", " | Prism of width: float * height: float * faces: int\n", " \n", "let rect = Rectangle(length = 1.3, width = 10.0)\n", "let circ = Circle (1.0)\n", "let prism = Prism(width = 5.0, height = 2.0, faces = 3)\n", " \n", "prism" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pattern matching\n", "\n", "The best way to work with DUs is pattern matching. Using the previously-defined type definitions, we can model getting the height of a shape." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let height shape =\n", " match shape with\n", " | Rectangle(width = h) -> h\n", " | Circle(radius = r) -> 2.0 * r\n", " | Prism(height = h) -> h\n", " \n", "let rectHeight = height rect\n", "let circHeight = height circ\n", "let prismHeight = height prism\n", "\n", "printfn \"rect is %0.1f, circ is %0.1f, and prism is %0.1f\" rectHeight circHeight prismHeight" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can pattern match on more than just discriminated unions. Here we write a recursive function with `rec` to process lists:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "// See if x is a multiple of n\n", "let isPrimeMultiple n x =\n", " x = n || x % n <> 0\n", " \n", "// Process lists recursively.\n", "// '[]' means the empty list.\n", "// 'head' is an item in the list.\n", "// 'tail' is the rest of the list after 'head'.\n", "let rec removeMultiples ns xs =\n", " match ns with\n", " | [] -> xs\n", " | head :: tail ->\n", " xs\n", " |> List.filter (isPrimeMultiple head)\n", " |> removeMultiples tail\n", " \n", "let getPrimesUpTo n =\n", " let max = int (sqrt (float n))\n", " removeMultiples [2 .. max] [1 .. n]\n", " \n", "// Primes up to 25\n", "getPrimesUpTo 25" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Options\n", "\n", "A built-in DU type is the F# option type. It is used prominently in F# code. Options can either be `Some` or `None`, and they're best used when you want to account for when there may not be a value." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let keepIfPositive a =\n", " if a > 0 then\n", " Some a\n", " else \n", " None\n", " \n", "keepIfPositive 12" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Options are often used when searching for values. Here's how you can incorporate them into list processing:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "let rec tryFindMatch predicate lst =\n", " match lst with\n", " | [] -> None\n", " | head :: tail ->\n", " if predicate head then\n", " Some head\n", " else\n", " tryFindMatch predicate tail\n", " \n", "let greaterThan100 x = x > 100\n", "\n", "tryFindMatch greaterThan100 [25; 50; 100; 150; 200]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Parallel Programming\n", "\n", "For more CPU-intensive tasks, you can take advantage of built-in parallelism:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "#!time\n", "\n", "let bigArray = [| 0 .. 100_000 |]\n", "\n", "let rec fibonacci n = if n <= 2 then n else fibonacci (n-1) + fibonacci (n-2)\n", "\n", "// We'll use the '%A' print formatter for F# constructs for these results, since they are enormous\n", "let results =\n", " bigArray\n", " |> Array.Parallel.map (fun n -> fibonacci (n % 25))\n", "\n", "printfn \"%A\" results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because F# functions are first-class values, you can trivially do things like initialize expensive functions in parallel with the `Array.Parallel` module. This is quite common in numerics-intensive F# code.\n", "\n", "Here's an example where you can compute as many fibonacci numbers as there are threads in your current process. The `#!time` magic command shows the wall-clock time it took to perform the operation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "fsharp" } }, "outputs": [], "source": [ "#!time\n", "\n", "// Restrict the number of threads to a max of 25\n", "let nThreads = min 25 Environment.ProcessorCount\n", " \n", "Array.Parallel.init nThreads fibonacci" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's also worth noting how much faster the second cell ran than the first one. This is because it doesn't use call `printfn` with the `%A` formatter. Although this kind of formatting is very convenient in F#, it comes at a performance cost!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Learn more\n", "\n", "There are a lot of learning resources for F# that go far beyond this notebook.\n", "\n", "Check out the [F# docs homepage](https://docs.microsoft.com/dotnet/fsharp/) for an organized set of learning material.\n", "\n", "To learn more about using F# with this Jupyter kernel, we recommmend the following notebooks:\n", "\n", "* [The F# notebook programming model](Docs/Programming-model.ipynb)\n", "* [Displaying output](Docs/Displaying-output.ipynb)\n", "* [Importing packages](Docs/Importing-packages.ipynb)\n", "* [Plotting with Xplot](Docs/Plotting-with-Xplot.ipynb)\n", "\n", "For more advanced samples, check out the following:\n", "\n", "* [HousingML](Samples/HousingML.ipynb)* [Getting started with data frames](Samples/DataFrame-Getting-Started.ipynb)* [GitHub Repo Statistics](Samples/Repo-Statistics.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (F#)", "language": "F#", "name": ".net-fsharp" }, "language_info": { "file_extension": ".fs", "mimetype": "text/x-fsharp", "name": "C#", "pygments_lexer": "fsharp", "version": "4.5" } }, "nbformat": 4, "nbformat_minor": 4 }