From OOP to Functional Programming

czyli F# oczami programisty C#

 

Michal Franc

Pragmatic Developer @ JustGiving

www.mfranc.com

@francmichal

  • PWR
  • 4 lata Wrocek, 1 rok Londyn @JustGiving
  • 90% backend / 10% frontend
  • 70% dev / 30 % lead
  • self-organizing teams believer
  • Początkujący speaker i Fsharper
  • bloger - www.mfranc.com
  • dotnetconf.pl
  • Vim lover

O mnie

Agenda

  • Paradygmaty programowania
  • Funkcjonalne vs C#-powo
  • Sample C# vs F#
  • F# magic

Imperatywnie

Instrukcja po instrukcji

mutacja danych

C, C++

 

Nacisk na polecenia dla komputera

Obiektowo

rozwinięcie imperatywnego podejścia

dodanie konstrukcji pozwalających
modelować kod za pomocą obiektów

C++, Java, C#

renesans dzięki DDD

Funkcjonalnie

bez mutacji danych

Funkcje zwracające funkcje przyjmujące funkcje

Scala, Haskell, Clojure, F#

Nacisk na funkcje

Deklaratywnie

Dla danego wejścia opisanie żądanego wyjścia

SQL

Manipulacje na zbiorze danych

Imperatywnie vs Funckjonalnie

                    (obiektowo)

brak mutacji danych

rekurencja, funkcje, pattern matching

funkcje, record types, union types

kompozycja funkcji

 

mutacje danych

pętle, funkcje, ify

obiekty, struktury

dziedziczenie, kompozycja, polimorfizm obiektów

Funckjonalny C#

Linq - deklaratywnie + funkcjonalnie

Anonimowe funkcje

wyrażenia lambda / delegate 

type inference - var, dynamic

tuple

C# 

Hybryda z naciskiem na programowanie obiektowe

F#

Hybryda z naciskiem na programowanie funkcjonalne

Dlaczego F# ?

  • immutability by default
  • hybrydowy język
  • Nie trzeba rzucać się na głęboką wodę
  • małymi krokami funkcjonalnie
  • .NET interop
  • wsparcie VS ( w pewnym zakresie )

Blokery

  • skladnia
  • platforma
  • nowe trudne pojecia
  • OOP experience
  • przyzwyczajenie do mutacji danych
  • Functional Thinking
  • intuicja / doświadczenie
  • R#

Funkcjonalne Transformacje

C# vs F#

Demo

Licznik lista

C# - count

var current = 0;

foreach(elem in list) {
    current++;
}
List - Count

public void Add(T item) {
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            _items[_size++] = item;
            _version++;
        }

F# - count

Mutacja Danych

let list = [1..100]

let count list =
    let mutable counter = 0;
    for i in list do
        counter <- counter + 1
    counter
        
count list

F# - count

let list = [1..10000000]

let rec count list =
    match list with
        | i::tail -> 1 + (count tail)
        | [] -> 0
        
count list       

Rekurencja!

StackOverflow!

F# - count

let list = [1..10000000]

let rec count acc list =
    match list with
        | i::tail -> (count (acc + 1) tail)
        | [] -> acc
        
count 0 list     

Tail Recursion

Demo

Enkapsulacja

 C# - object

    public class Human
    {
        public int Life { get; set; }

        public Human()
        {
            this.Life = 100;
        }

        public void TakeHit(int val)
        {
            this.Life -= val;
        }
    }

 F# - object

type Human() =
     let mutable _life = 100
     member this.takeHit value = _life <- _life - value
     member this.Life
            with get () = _life

let human = new Human()
type Human(life) =
     let _life = life
     member this.takeHit value = new Human(_life - value)
     member this.Life
            with get () = _life

human.takeHit 10
human.Life
let human = new Human(100)
let newHuman = human.takeHit 10
newHuman.Life

F# - record type 

type Item = {
    Id : int
    Name : string
    IsAwesome : bool
    IsActive : bool 
}
let createDefault = {
    Id = 1
    Name = "first"
    IsAwesome = true
    IsActive = true
} 
let first = { createDefault with Name = "first" }
let second = { first with Quantity = 5 }
let third = { second with IsActive = false; IsAwesome = false }
let isAwesomeButNotActive item = 
    match item with
    | { IsActive = false; IsAwesome = true } -> true
    | _ -> false
    
isAwesomeButNotActive { createDefault with IsActive = false }

F# - record type

type Human = {
    Life : int
}

let createNew = {
    Life = 100
}

let takeHit value human = {
    human with Life = human.Life - value
}

let human = createNew
let newHuman = takeHit 10 human

F# - record type

type Human = {
    Life : int
} with member this.takeHit value = {
    this with Life = this.Life - value
}

let createNew = {
    Life = 100
}

let human = createNew
let newHuman = human.takeHit 10

C# - edge cases

public void TakeHit(int val)
{
    if (val < 0) throw new ArgumentOutOfRangeException();

    this.Life -= val;

    if (this.Life < 0) this.Life = 0;
}

F# - edge cases

let takeHit value human = 
    if value < 0 then raise (System.ArgumentException())
    {
        human with Life = if (human.Life - value) < 0 
                          then 0 else human.Life - value
    }
let takeHit value human = 
    if value < 0 then invalidArg "value" "cannot be less than 0"
    ...

F# - edge cases

let takeHit value human = 
    if value < 0 then invalidArg "value" "cannot be less than 0"
    {
        human with Life = match (human.Life, value) with
                            | (l, v) when l - v < 0 -> 0
                            | (l, v) -> l - v
    }
let takeHit value human = 
    if value < 0 then invalidArg "value" "cannot be less than 0"
    {
        human with Life = match human.Life - value with
                            | difference when difference < 0 -> 0
                            | difference -> difference
    }

C#

public void Attack(Human target)
{
     target.TakeHit(this.Strength); 
}
public void Attack(Human target)
{
     target.TakeHit(this.Strength);
     this.Stamina -= 10;
     if (this.Stamina < 0) this.Stamina = 0;
}
public void Attack(Human target)
{
    if (this.Stamina - 10 < 0) return;

    target.TakeHit(this.Strength);
    this.Stamina -= 10;
    if (this.Stamina < 0) this.Stamina = 0;
}

F#

let attackTarget human target =
    takeHit human.Strength target
let attackTarget human target =
    (
        takeHit human.Strength target, 
        { human with Stamina = human.Stamina - 10 }
    )

let human = createNew
let target = createNew
let (newHuman, newTarget) = attackTarget human target
let difference0 one two = match one - two with
                            | difference when difference < 0 -> 0
                            | difference -> difference 
let attackTarget human target =
    (
        takeHit human.Strength target, 
        { human with Stamina = difference0 human.Stamina 10 }
    )
let private (-@) one two = match one - two with
                            | difference when difference < 0 -> 0
                            | difference -> difference 
let attackTarget human target =
    (
        takeHit human.Strength target, 
        { human with Stamina = human.Stamina -@ 10 }
    )

NULL in F#

*da się poszaleć z nullem tak jak da się włączyć wskaźniki w C#

[<AllowNullLiteral>] - interop

F# Patterns

let basic item =
    printf "%i" item
    
let newLine item =
    printf "\n%i" item

type Strategy(displayFunction) = 
    member this.DisplayFunction list = 
            List.iter displayFunction list

Strategy(basic).DisplayFunction [1..6]
Strategy(newLine).DisplayFunction [1..6]

F# Patterns

type A private () =
    static let instance = A()
    static member Instance = instance
    member this.Action() = printfn "singleton"

A.Instance.Action()

F# Interfaces

type ICalc = 
    abstract member Calc: int -> int -> int
    
type Calculator() =
    interface ICalc with 
        member this.Calc x y = 
            x + y
         
type ICalc = 
    abstract member Calc: int -> int -> int

let addCalc = 
    { new ICalc with member this.Calc x y = x + y }
        
let calc = addCalc
calc.Calc 1 1
let calc = new Calculator() :> ICalc
calc.Calc 1 1

F# Interfaces

type CalcService = {
    Add : int -> int -> int
    Substract : int -> int -> int
    Multiply : int -> int -> int
    Divide : int -> int -> int
}

let service = {
    Add = fun x y -> x + y
    Substract = fun x y -> x - y
    Multiply = fun x y -> x * y
    Divide = fun x y -> x / y
}

C# Interfaces

public class ImageRepo
{
   private IImageProvider imageProvider;

   public ImageRepo(IImageProvider imageProvider)
   {
        this.imageProvider = imageProvider;
   }

   public byte[] GetImage(string imageName)
   {
       return this.imageProvider.GetImage(imageName);
   }
}

var fromDB = new ImageRepo(new IMageFromDataBaseProvider());
var fromUrl = new ImageRepo(new IMageFromUrlProvider());
var fromSrv = new ImageRepo(new IMageFromFileProvider());

F# Interfaces

type ImageFrom = URL | SRV | DB

let imageRepoBuilder imageFrom =
    match imageFrom with
    | URL -> imageRepo imageFromUrl
    | SRV -> imageRepo imageFromSrv
    | DB -> imageRepo imageFromDb
 
let imageFrom imageType name =
    imageRepoBuilder imageType name
    
imageFrom URL "test.jpg"
imageFrom DB "test.jpg"
imageFrom SRV "test.jpg"

Czy warto ?

  • Dojrzałe community
  • inne podejście
  • nowe rozwiązania
  • szersze spojrzenie
  • większa wiedza
  • Scala, Clojure, Haskell - przystępniejszy
  • lepsze niż kolejny framework
  • blows your mind

Komercyjnie ?

  • rozkręca sie
  • specjalizacja - koder + data science, math, domain
  • CS we Wrocku
  • W Londku sporo ofert -http://functionalworks.ghost.io/ 150% mediania vs C#
  • w JG próbujemy

http://i.imgur.com/FLf5J6f.jpg

QA

More

http://fsharpforfunandprofit.com/

http://fsharpforfunandprofit.com/rop/

http://www.tryfsharp.org/

http://www.amazon.com/Real-World-Functional-Programming-With-Examples/dp/1933988924

http://fsharp.org/

https://sergeytihon.wordpress.com/category/f-weekly/

http://www.cs.uni.edu/~wallingf/blog-images/computing/make-let-not-var-small.jpg

End

Michal Franc

www.mfranc.com

@francmichal

From OOP to Functional Programming [PL]

By Michal Franc

From OOP to Functional Programming [PL]

From OOP to Functional Programming

  • 631

More from Michal Franc