Introduction

Welcome to the F# notes book!

Motivation

One of the most painful parts of programming is debugging. How can we make our code have less bugs? Functional Programming is a style of programming that can catch many bugs!

Why do I love F#?

  • F# catches many bugs, making coding so much more enjoyable
  • F# is light like Python
  • F# has Microsoft's Dotnet ecosystem making it easy to get helpful libraries!

References

F# for Fun and Profit

Microsoft

Cheatsheet

F# in VSCode from Scratch

F# in VSCode from Scratch

FAKE and Paket vs Rust Cargo

There's an Ionide plugin and Ionide-FAKE and Ionide-Paket

dotnet new lists all templates

# Create a F# Console Application
dotnet new console -lang F# -o src/MyConsoleApp

# Create a class library
dotnet new classlib -lang F# -o src/MyLibrary

Editing the fsproj file

In MyLibrary.fsproj xml add:

<ItemGroup>
    <Compile Include="Library.fs" />
    <Compile Include="NewFile.fs" />
  </ItemGroup>

To add MyLibrary.fsproj to MyConsoleApp.fsproj add:

<ItemGroup>
      <ProjectReference Include="..\MyLibrary\MyLibrary.fsproj" />
  </ItemGroup>

or run

dotnet add src/MyConsoleApp/MyConsoleApp.fsproj reference src/MyLibrary/MyLibrary.fsproj

Building

To build, run:

dotnet build src/MyConsoleApp/MyConsoleApp.fsproj

This gives you a .dll file to run:

dotnet src/MyConsoleApp/bin/Debug/netcoreapp3.0/MyConsoleApp.dll

Running

dotnet run -p src/MyConsoleApp/MyConsoleApp.fsproj Scott

This runs your console application with args Scott.

Add Nuget References

Add reference to MyConsoleApp.fsproj:

<ItemGroup>
    <PackageReference Include="Argu" Version="5.0.1" />
</ItemGroup>
  • If you get warning Detected package downgrade: FSharp.Core, add a <PackageReference> to FSharp.Core
    • Add minimum version: <PackageReference Include="FSharp.Core" Version="4.3.2" />

Alternatively run

dotnet add src/MyConsoleApp/MyConsoleApp.fsproj package Argu

# if you have package downgrade error.
dotnet add src/MyConsoleApp/MyConsoleApp.fsproj package FSharp.Core

Package References with Paket

Run:

paket init
paket add --project src/MyConsoleApp/MyConsoleApp.fsproj Argu

It automatically handles dependencies! Wow!

If you have nuget packages, you can convert with:

paket convert-from-nuget

Adding Tests with Expecto

We can write it manually:

dotnet new console -lang F# -o tests/MyTests
dotnet add tests/MyTests/MyTests.fsproj package Expecto
dotnet restore tests/MyTests/MyTests.fsproj

And edit Program.fs to:

open Expecto

[<EntryPoint>]
let main argv =
    Tests.runTestsInAssembly defaultConfig argv

Finally run:

dotnet run -p tests/MyTests/MyTests.fsproj

Using Expecto Template

This feels better.

dotnet new -i Expecto.Template::12.0.0
dotnet new expecto -o tests/MyTests

dotnet run -p tests/MyTests/MyTests.fsproj

F# Ecosystem vs Rust's Cargo

Paket

# init paket
paket init

# add a dependency
paket add <package> -p <project>
# or 
paket add <package> -i #(to be prompted on which projects need to have the reference added

# remove a dependency
paket remove <package>

# update all dependencies
paket update

# update single dependencies
paket update <package>

Running

# packaging?
dotnet pack

# publish for deployment?
dotnet publish

# build
dotnet build

# run
dotnet run

FAKE

For complex MAKE like build, packaging, deployment, and testing, however you can also just call CLI tools.

In essence, it's a scripting tool.

Tooling

VS Code Ionide

YouTube Link

  • Install dotnet core
  • Install Ionide-fsharp

F# Scripting

let vowels = set ['a'; 'e'; 'i'; 'o'; 'u']

let toPigLatin (str: string) =
    let firstChar = str.[0]
    if vowels.Contains firstChar then
        str + "yay"
    else
        str.[1..] + string firstChar + "ay"

Highlight the text and hit alt-enter. This sends you to F# interactive

// with fsi active, when you are on a line, you can hit alt enter and it sends it to fsi!
toPigLatin "Phillip"
toPigLatin "Cecil"

You can change all symbols toPigLatin to changeToPigLatin. Hit right click and select change symbols

Another is Find all references!

There is a go to shortcut too.

To run the fsx program in shell, run

dotnet fsi vowels.fsx

Info Panel

Hit ctrl-p and enter >#F open info panel

Try it on each word in:

let firstResult = aps |> Array.sumBy (fun struct(x, _) -> x)

  • On the right as you explore Array.Parallel, it gives you info on it!

To run in release:

dotnet run -c release

FAKE and Paket

FAKE + Paket Talk

FAKE

  • build tool for .NET and mono
  • DSL in F#

tutorial available

dotnet new tool-manifest
dotnet tool install fake-cli -g

Running FAKE

@echo off
cls
.paket/paket.exe restore
packages/FAKE/tool/Fake.exe build.fsx
pause

Hello World

  • Target "Default" which prints Hello World from FAKE

Cleaning Up

  • Runs CleanDir on the buildDir

Compiling the Application

  • Build - find files ending with .csproj which MSBuildRelease can use
  • "Clean" has the dependencies

Compiling Test Projects

  • If your project has tests

Running Tests

  • After building the tests, run the tests!

  • Run the tests in parallel with NUnitParallel

Other Functions

  • Creating Nuget Packages
  • (what is a solution file?)

Live Release

  • Example of Paket's Target "Clean"

  • The build and release succeeded!!! Live!

Paket

  • Another packet manager like Nuget.
  • Nuget has not global info :/

  • What version did I install?!?!?

  • Only a couple of files

  • paket.dependencies - what dependencies does your project have?

  • paket.lock - the lock file computes a resolution for all your dependencies

  • paket.references

Paket commands

paket install # install everything
paket outdated # is anything outdated
paket why nuget [packagename] # where is this dependency required
paket update # updates `paket.lock`
paket restore # restores dependencies (important, don't know how lol?)
paket simplify # tries to simplify - it might be wrong

  • Can have groups, packages for building, packages for testing

  • In line 16, I think

  • The paket references!

FAKE Getting Started

Install FAKE globally dotnet tool install fake-cli -g

In your program directory add build.fsx:

#r "paket:
nuget Fake.Core.Target //"
// include Fake modules, see Fake modules section

open Fake.Core

// *** Define Targets ***
Target.create "Clean" (fun _ ->
  Trace.log " --- Cleaning stuff --- "
)

Target.create "Build" (fun _ ->
  Trace.log " --- Building the app --- "
)

Target.create "Deploy" (fun _ ->
  Trace.log " --- Deploying app --- "
)

open Fake.Core.TargetOperators

// *** Define Dependencies ***
"Clean"
  ==> "Build"
  ==> "Deploy"

// *** Start Build ***
Target.runOrDefault "Deploy"

Run sudo fake run build.fsx. You can also just run sudo fake build. When you download a new module, delete the build.fsx.lock file.

Notes:

Fake.IO.FileSystem //

open Fake.IO

Shell.cleanDir

Fake.DotNet.MSBuild // builds csproj-files under /src/app

Target.create "BuildApp" (fun _ ->
    !! "src/app/**/*.csproj"
    |> MSBuild.runRelease id buildDir "Build"
    |> Trace.logItems "AppBuild-Output: "
)

// !! is a pattern to scan for

Starting a new Project with a Template

Here is a Fake Template!

Run:

mkdir proj_dir
cd proj_dir

sudo dotnet new fake
./fake.sh build

This doesn't have any of the files like "src/**/bin" or "src/**/*.*proj". Why isn't it like Rust?

This may be helpful.

Snippets

Exec (Like subprocess.run)

open System.Diagnostics

module Subprocess =
    let private escapedArgs (args: string) =
        args.Replace("\"", "\\\"") 
    
    let private runBody (proc: Process) (args: string) =
        proc.StartInfo.FileName <- "/bin/bash"
        proc.StartInfo.Arguments <- sprintf  "-c \"%s\"" (escapedArgs args)
        proc.Start() |> ignore
        proc.WaitForExit()
        proc

    let run(args: string) =
        let proc = new Process()
        runBody proc args |> ignore    
    
    let checkOutput(args: string): string =
        let mutable proc = new Process()
        // redirect output
        proc.StartInfo.RedirectStandardOutput <- true
        proc.StartInfo.RedirectStandardError <- true
        proc <- runBody proc args
        proc.StandardOutput.ReadToEnd()

// Subprocess.run("ls")
// printfn "output:\n%s" (Subprocess.checkOutput("ls"))

IFSharp with Paket

#load "Paket.fsx"
Paket.Version ["Argu", "6.0.0"]
#load "Paket.Generated.Refs.fsx"

open Argu

Links

Debugging

Practice

F# by Example

Go by Example

Automate the Boring Stuff with F#

Automate the Boring Stuff with Python

Functional Programming

Types, and Why You Should Care

Types, and Why You Should Care - Yaron Minsky

  • Left: typed, Right: dynamicly typed
  • Performance

  • Typed Langs are faster
  • They give info for humans to understand.
    • Human Centered Computing

How Types Systems Hurt

  • Java is verbose :(
  • It slows you down. I can hack things together in Python but is slow to get done in Java

How Types Help!

  • In Python d2.get(key) gives you none. This is a logic error. You wrote something wrong.

  • In OCaml, this is now a type error! The compiler expected type: 'a option instead of Python's d2.get(key) which could have been none.

  • We can now handle all cases with pattern matching. If you miss an edge case, the compiler will tell you!

  • Strong Types:
    • Catch Errors: Less debugging! Many types of errors can be caught with a strong type system.
    • Have Less Surprises: Your thinking match what the code actually does. Types guide your thinking and let the compilers check the code matches your thinking!
    • Making Changes Doesn't Add New Bugs: Places where your code will affect and needs to be changed will be a type error!
  • "It feels like all the bugs go away! A lot of the bugs you spend a lot of time on feel like it vanishes in a puff of smoke."

Good Features in Type Systems

  • Type Inference:
  • Algebraic Data Types:
    • Product Type:
      • Structs, Record
      • This item AND this item AND this item
    • Sum Type:
      • Not common!
      • This item OR this item OR this item
      • Helps check you are handling all edge cases!
  • Garbage Collection:
    • In LISP 1950
    • Java 1990! 40 years.
    • Imagine Algebraic Data Types in 2060!

Product Types:

In C:

typedef struct {
   int    account_number;
   char   *first_name;
   char   *last_name;
   float  balance;
} account;

in F#:

type Contact = {
    Name: PersonalName 
    PrimaryContactInfo: ContactInfo
    SecondaryContactInfo: ContactInfo option
}

Sum Types: @TODO

More Advance

  • First Class Module
    • pass it around a function, store in a data structure
    • Generalized Algebraic Datatypes (GADT)
      • GADT of TCP of NASDAQ_message of add_order_message
      • TCP | UDP
      • Layers of OR and ANDS
      • GADT lets you do this without allocation

Adapting OCaml

  • Tooling isn't hot. You have to invest a lot into it.
    • Documentation
    • IDE features: Go to definition, auto-completion
  • We're not going to move fast and break things, we're
    • They have a focus on quality control - at their scale of money
  • Went from VBA/Excel to Java to OCaml

on F#

  • It's a car crash. Mixes Dotnet and OCaml
    • Adds complexity
    • OCaml is simple!

Gradual Typing

  • Making a language that does both is hard
  • Gradual Python, Typescript, Php to Hack
  • Can be unsound, it's not always right
  • Idiom of lipstick on a pig

On Testing

  • Types are good, testing is important too.
    • Many things tested manually will be caught for you by the type system.
    • It "snaps into place" testing a few examples encapsulate all the behavior

Domain Modeling Made Functional

YouTube

  • type Contact in our design
  • What is optional (MiddleInitial)
  • What are the constraints? (string can't be more than 50 chars)
  • What is the domain logic?

Domain Driven Design

Care about the domain.

  • The goal is comunication.

Communication

  • Different contexts same word. How can we be clear in communication?

Card Game example!

module CardGame =
	type Suit = Club | Diamond | Spade | Heart

	type Card = Suit * Rank // pair

	type Hand = Card list

	type Player = {
		Name: string;
		Hand: Hand;
	}

	type Deal = Deck -> (Deck * Card) // deal is an action where you takes a deck and get a card and the rest of the deck
	type PickupCard = (Hand * Carol) -> Hand

F# Type System

  • Composable Type System

  • int -> int

  • Pair Type
  • type Birthday = Person * Data

  • Choice type

  • Really useful for domain modeling

  • Use Pattern matching!

  • It's a domain modeling unit test!!!

Optional Values

  • Choice of Some Value or None Value

  • Generic Type!

  • This keeps them distinct!

Constraiened Values

This is really powerful.

  • Constained String

ConstrainedTypeExamples.fsx Gist

module ConstrainedTypes =
    open System

    // ---------------------------------------------
    // Constrained String50 (FP-style)
    // ---------------------------------------------

    /// Type with constraint that value must be non-null
    /// and <= 50 chars.
    type String50 = private String50 of string

    /// Module containing functions related to String50 type
    module String50 =

        // NOTE: these functions can access the internals of the
        // type because they are in the same scope (namespace/module)
        
        /// constructor
        let create str = 
            if String.IsNullOrEmpty(str) then
                None
            elif String.length str > 50 then
                None
            else
                Some (String50 str)

        // function used to extract data since type is private
        let value (String50 str) = str

module Client = 
    open ConstrainedTypes 


    // ---------------------------------------------
    // Constrained String50 (FP-style)
    // ---------------------------------------------

    let s50Bad = String50 "abc" 
    // ERROR: The union cases or fields of the type 'String50' are not accessible from this code location
    
    let s50opt = String50.create "abc" 
    s50opt 
    |> Option.map String50.value 
    |> Option.map (fun s -> s.ToUpper())
    |> Option.iter (printfn "%s")

  • Type Validated

  • Create a new type

  • Well Modeled in the Domain! Less Unit Tests
type EmailAddress = ...

type VerifiedEmail = VerifiedEmail of EmailAddress

// ?
type EmailContactInfo = 
| Unverified of EmailAddress
| Verified of VerifiedEmail

type PersonName = {
	FirstName: String50
	MiddleInitial: String1 option
	LastName: String50
}

type Contact = {
	Name: PersonalName
	Email: EmailContactInfo
}

Make Illegal State Unrepresentable

  • Use a choice type
  • Self-Documenting Code

  • They really wanted to have one way of being contacted.
  • You could have two email addresses but this is what the business was looking for so it's ok.

Review

Link

Why OCaml

Why OCaml - Jane Street YouTube

F# Code I Love

F# Code I Love - Don Syme

What is good F# practice?!

What is F#?

It's high performance from Microsoft Dotnet!

But also Fable/JavaScript!

dotnet new -lang F#
dotnet build

  • 350,000 to 30,000 lines!

  • 3000 null checks!

  • 2000 try catches!

Good F# Code

Pipelining

  • Pipelining!

Domain Modeling

  • Domain Modeling
    • Expression Modeling!

  • Web Request

  • Request to iTunes!

Data Scripting

  • flow of data and code

User Interfaces

  • User Interfaces! (Web, Mobile)
    • Model-View-Update

  • As a website! View is now a function
  • Fable, SAFE stack, Elmish
  • Fabulous for Xamarin

Composition

  • The compiler just composes functions together!

  • Giraffe Functional Web Server

Bad F# code. Don't Do's!

  • Just use parenthesis. Read them as tuples.

Back pipe

  • <| is just confusing. Use parenthesis

Point Free Code

  • weird operators like >> or >>=

Fold considered harmful

Objects Programming

Type Providers

  • Take a HTMLProvider now you have the page type information!

Computational Expression

  • Use a for loop list

Async

Blogs

Maybe High Quality

Others