Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val id : x:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.id
namespace Microsoft.FSharp.Data
namespace System
module Option

from Microsoft.FSharp.Core
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
Multiple items
module Result

from Microsoft.FSharp.Core

--------------------
type Result<'T,'TError> =
  | Ok of ResultValue: 'T
  | Error of ErrorValue: 'TError

Full name: Microsoft.FSharp.Core.Result<_,_>
Multiple items
val Failure : message:string -> exn

Full name: Microsoft.FSharp.Core.Operators.Failure

--------------------
active recognizer Failure: exn -> string option

Full name: Microsoft.FSharp.Core.Operators.( |Failure|_| )
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.decimal

--------------------
type decimal = System.Decimal

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Multiple items
type DateTime =
  struct
    new : ticks:int64 -> DateTime + 10 overloads
    member Add : value:TimeSpan -> DateTime
    member AddDays : value:float -> DateTime
    member AddHours : value:float -> DateTime
    member AddMilliseconds : value:float -> DateTime
    member AddMinutes : value:float -> DateTime
    member AddMonths : months:int -> DateTime
    member AddSeconds : value:float -> DateTime
    member AddTicks : value:int64 -> DateTime
    member AddYears : value:int -> DateTime
    ...
  end

Full name: System.DateTime

--------------------
System.DateTime()
   (+0 other overloads)
System.DateTime(ticks: int64) : unit
   (+0 other overloads)
System.DateTime(ticks: int64, kind: System.DateTimeKind) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, calendar: System.Globalization.Calendar) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: System.DateTimeKind) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: System.Globalization.Calendar) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: System.DateTimeKind) : unit
   (+0 other overloads)
property System.DateTime.Now: System.DateTime
System.DateTime.ToString() : string
System.DateTime.ToString(provider: System.IFormatProvider) : string
System.DateTime.ToString(format: string) : string
System.DateTime.ToString(format: string, provider: System.IFormatProvider) : string
namespace System.Threading
Multiple items
type Thread =
  inherit CriticalFinalizerObject
  new : start:ThreadStart -> Thread + 3 overloads
  member Abort : unit -> unit + 1 overload
  member ApartmentState : ApartmentState with get, set
  member CurrentCulture : CultureInfo with get, set
  member CurrentUICulture : CultureInfo with get, set
  member DisableComObjectEagerCleanup : unit -> unit
  member ExecutionContext : ExecutionContext
  member GetApartmentState : unit -> ApartmentState
  member GetCompressedStack : unit -> CompressedStack
  member GetHashCode : unit -> int
  ...

Full name: System.Threading.Thread

--------------------
System.Threading.Thread(start: System.Threading.ThreadStart) : unit
System.Threading.Thread(start: System.Threading.ParameterizedThreadStart) : unit
System.Threading.Thread(start: System.Threading.ThreadStart, maxStackSize: int) : unit
System.Threading.Thread(start: System.Threading.ParameterizedThreadStart, maxStackSize: int) : unit
System.Threading.Thread.Sleep(timeout: System.TimeSpan) : unit
System.Threading.Thread.Sleep(millisecondsTimeout: int) : unit
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
type obj = System.Object

Full name: Microsoft.FSharp.Core.obj
module Seq

from Microsoft.FSharp.Collections
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.filter
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map

F# |> LV




Alexander Prooks - @aprooks



apaleo


https://apaleo.com/

Defining a problem

What can go wrong?

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
public class CustomerService
{
    public static Result CreateCustomer(
                    string id, 
                    string username, 
                    string email, 
                    string name,
                    string lastName, 
                    string phone, 
                    string password)
    {
        //Validate
        //Persist
    }

}

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
//...
CustomerService.CreateCustomer(
    "asdfgh-1234-1234",
    "aprooks",
    "aprooks@live.ru",
    "Prooks",
    "Alexander",
    "somePass",
    "79062190016");

Introduce Parameter Object (c) Fowler

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
public class CreateCustomerDto
{
    public CreateCustomer(
                    string id, 
                    string username, 
                    string email, 
                    string name,
                    string lastName, 
                    string phone, 
                    string password)
    {
        this.Id = id;
        this.Username = username;
        this.Name = name;
        this.Surname = lastName;
        this.Phone = phone;
        this.Password  = password;
    }
    public string ID {get;}
    public string Username {get;}
    //etc..
}

Uniform interface

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
var result = CustomerService.Handle(
                  new CreateCustomerDto(  
                            id: "Id",
                            username: "Aprooks",
                            email: "aprooks@live.ru",
                            phone: "79062190016"
                            name: "Alexander",
                            lastName: "Prooks",
                            password: "helloWorld"
                ));
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
    // middleware
    public Result Handle<TService, T>(TService service, T request){
        Log.Debug("Handled {request}",request);

        var validator = ValidationFactory.GetValidator<T>();
        if(validator!=null){
            var validationResult = validator.Validate(request);
            if(!result.IsValid)
                return validationResult.ToError();
        }
        object result;
        try{
            result = Polly.Handle<TimeoutException>()
                          .Retry(5)
                          .Execute(()=> service.Handle(request));
        }
        catch(Exception ex)
        {
            return ex.ToError()
        }
        return Result.Handled(result);
    }

Functional = data + (pure) functions


F# = types + functions + imperative fallback

Records types

  • Flat data
  • All fields are required => "AND type"
  • Immutable by default (like everything else)

Definition

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type CreateCustomer = {
    Id: string
    Username: string
    Email: string
    Phone: string
    Name: string
    LastName: string
    Password: string
}

Generated C# code

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
public sealed class CreateCustomer {
    IEquatable<CreateCustomer>,
    IStructuralEquatable,
    IComparable<CreateCustomer>,
    IComparable,
    IStructuralComparable
 
    //props
    //Constructor
    //Interfaces implementations
}

Full comparison

Init with data

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let dto = {
    Id= "test"
    Username= "aprooks"
    Email= "aprooks@live.ru"
    Phone= "79062190016"
    Name= "Alexander"
    LastName= "Prooks"
    Password= "secret"
}

Syntax sugar

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let copyPasted = {
    Id= "test"
    Username= "aprooks"
    Email= "aprooks@live.ru"
    Phone= "79062190016"
    Name= "Alexander"
    LastName= "Prooks"
    Password= "secret"
}
copyPasted = dto //true

let b = {a with Id="Test2"} //copy

b = a //false

Aliases aka document your types

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// I'm prototyping and not sure what it will be
type Id = NotImplementedException 
type Email = string
type Username = string

type CreateCustomer2 = {
    Id: Id
    Username: Username
    Email: Email
    Phone: string
    Name: string
    LastName: string
    Password: string
}

Discriminated union

  • Pick only one of: "OR" type
1: 
2: 
3: 
4: 
5: 
6: 
// Choose strictly one
type ``Enum on steroids`` =
    | ``I am a valid case without data``
    | SomethingElse
    | ``I have data`` of Data
    | ``I am recursion`` of ``Enum on steroids``

Single-case aka data wrapper

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type Id = 
    | Id of string
type Email = Email of string
type Username = Username of string

type Customer = {
    id: Id
    username: Username
    email: Email
    phone: string
    name: string
    lastName: string
    password: string
}

Compile time validation

1: 
2: 
3: 
4: 
let id = Id "test"
let username = Username "test"

//id = username //compile error

Multiple case type (DU)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// I wish it never happened  
type SystemError =
    | DatabaseTimeout
    | Unauthorised

// service module
type CustomerServiceError = 
    | UserAlreadyExists

// Composition root level
type ApplicationErrors = 
    | System of SystemError
    | CustomerService of CustomerServiceError
    | OtherService of OtherServiceError

Pattern matching to handle them all

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let toErrorMessage error = 
    match error with 
    | System err -> 
        match err with 
        | DatabaseTimeout err ->
            (HttpStatus.InternalServerError, "Ooops :(")
        | Unauthorised err ->
            (HttpStatus.Unauthorised, "Go away") 
    | CustomerService of err -> 
        match err with 
        | UserAlreadyExists -> 
            (HttpStatus.Conflict, "You are already registered")
    | OtherService of err -> 
        OtherService.ToErrorMessage err

DU Patterns

Option: Empty, but not null

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type Option<`a> = 
    | Some of `a
    | None

type User = {
    Id : UserId
    Address : Address option
}

let OnUserRegistered user = 
    /// blabla
    match user.Address with 
    | Some addr -> sendPostcard addr
    | None -> ignore()

Result: Done or error?

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure

let registerUser (load, save) user = 
    let dbUser = load user.Id
    match dbUser with 
    | None ->
        save user 
        Success(user.Id)
    | Some _ ->
        Error(UserService.AlreadyRegistered)

Railway oriented programming

Data everyone else can trust

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type Id = 
    private Id of string

module Id = 
    let create (input : string) = 
        if (input.Length > 10)
            // Imperative style:
            failwith new ArgumentException()
        else
            Id ( input.ToUpperInvariant() )

type UserId = UserId of Id

DDD

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type Percent = Percent of decimal
type Amount = Amount of decimal
type NumberOfNights = NumberOfNights of uint

type Discount = 
| ``Monetary per night`` of Amount
| ``Percent per night`` of Percent
| ``Monetary per stay`` of Amount
| ``Monetary for extra guest per night`` of uint * Amount
| ``Percent for extra stay`` of  NumberOfNights * Percent

// C#: public class MonetaryPerNight: IDiscount 
// blah blah

Domain modelling made functional


Where to buy

Types conclusion


  • No boilerplate
  • Readability
  • Type safety for free
  • Design with types
  • Unit test only interactions (functions)

Functions!

Reading signatures

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
// string -> string
let append (tail:string) string = "Hello " + tail

// inferred types:
let append tail = "Hello " + tail

// append 10 //compile error
append "world" //"Hello world"

// string -> string -> string
let concat a b = a + b

// unit -> int
let answer() = 42

// string -> unit
let devnull _ = ignore() 

Function as params

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
// (string -> unit) -> (unit->'a)
let sample logger f = 
    logger "started"
    let res = f()
    logger "ended"
    res

let consoleLogger output =
    printfn "%s: %s" (System.DateTime.Now.ToString("HH:mm:ss.f")) output

let result = sample consoleLogger (
                        fun () -> 
                            System.Threading.Thread.Sleep(500)
                            42
                    )

'a -> 'a -> 'a

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
// int -> int -> int
let sumInts (a:int) (b:int) = a+b     

// static int sum(decimal a, decimal b) = { return a+b}
// etc 

// 'a -> 'b -> 'c
//           when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c)
let inline sum a b = a + b   //WAT??

let d = 10m + 10m
let c = "test" + "passed"
let d = 100 + "test" //error

let (|>) x f = f x

Currying

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
// string -> string -> string
let concat x y = string.Concat(x,y)

// <=>

// string -> (string -> string)

// string -> string
let greet = concat "Hello"
// <=>
let greetVerbose w = concat "Hello" w

DI F# way

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
module Persistence = 

    let saveToDb connString (id,obj) = 
        // blah blah
        Success 

module CompositionRoot =
    
    let connectionString = loadFromConfig("database")
    let save = saveToDb connectionString

let result = save ("123",customer)

Data pipe [0]

1: 
2: 
3: 
4: 
5: 
6: 
let evenOnly = Seq.filter (fun n -> n%2=0) {0..1000}
let doubled = Seq.map ((*) 2) evenOnly
let stringified = Seq.map (fun d-> d.ToString()) doubled
let greeted = Seq.map greet stringified

// ["Hello 0","Hello 4", ...]

Data pipes [1]

1: 
2: 
3: 
4: 
5: 
let inline (|>) f x = x f

let evenF = (|>) ( {0..1000} ) ( Seq.filter (fun n -> n%2=0) )

let evenInfix = {0..1000} |> ( Seq.filter (fun n -> n%2=0) )

Piped data!

1: 
2: 
3: 
4: 
5: 
{0..1000}
|> Seq.filter (fun n -> n%2=0) //numbers
|> Seq.map ((*) 2) //evenOnly
|> Seq.map (fun d-> d.ToString()) //doubled
|> Seq.map greet //stringified

Real world like

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let handlingWrapper myHandler request = 
    request
    |> Log "Handling {request}"
    |> Validator.EnsureIsValid
    |> Deduplicator.EnsureNotDuplicate
    |> Throttle (Times 5) myHandler
    |> Log "Handling finished with {result}"

How to migrate

  • Utilities [Paket, Fake]
  • Contracts
  • Helpers
  • Tests [FsCheck, Expecto]
  • Code as client

Marketing

  • 2-20 times less code
  • Better reuse
  • Safer code => less bugs
  • Human readable code => faster feedback

F# in UI

F#/OCaml ecosystem

Q?