OOP i FP, przyjaciele czy wrogowie?

Mateusz Pokora

Mateusz

Pokora

software developer @

https://twitter.com/pokorson

https://github.com/pokorson

Programowanie obiektowe

  • Modelowanie obiektów posiadających dane oraz funkcje operujące na nich

Programowanie obiektowe

class Car 
    attr_accessor :name, :speed, :color

    def accelerate
        @speed += 10
    end

    def break
        @speed = 0
    end
end

myCar = Car.new "Toyota" 50 "black"

myCar.accelerate

Programowanie funkcyjne

  • Rozdzielenie na dane i funkcje operujące na danych
  • Immutable data
  • Izolacja side effect
  • Currying / Partial application
  • Kompozycja funkcji
  • Bardziej czytelny kod
  • Bardziej reużywalny kod
  • Łatwiejsze testowanie

Języki obiektowe a funkcyjne

  • Paradygmaty programowania określają sposób rozwiązywania pewnych problemów
  • Język programowania może wspierać pewne założenia lub nie, nie ogranicza nas to jednak we wdrażaniu tych założeń

Immutable data

  • Nie możemy zmienić raz zadeklarowanych struktur (obiekty, listy)
  • Nie możemy zmienić przypisania zmiennej
  • Tworzymy nowe obiekty ze zmodyfikowanymi polami

Immutable data

class Car 
    attr_accessor :name, :speed, :color

    def accelerate
        @speed += 10
    end

    def break
        @speed = 0
    end
end

1  myCar = Car.new "Toyota" 50 "black"
...
50 myCar.accelerate

Immutable data

class Car 
    attr_reader :name, :speed, :color

    def self.accelerateCar(car)
        self.new car.name, car.speed + 10, car.color
    end
end

1  myCar = Car.new "Toyota" 50 "black"
...
50 acceleratedCar = Car.accelerateCar myCar

Pure functions

  • Otrzymując te same argumenty zawsze zwracamy ten sam wynik
# pure
def add(a, b)
    a + b
end

# not pure
def persistCar(car)
   DB.save(car) 
end

Izolacja side effect

Przykładowe side effecty

  • Zapis do bazy danych
  • Zapisanie obrazka w S3
  • Wysłanie zapytania HTTP

Izolacja side effect

Brak mechanizmów w samym języku w Ruby

class Car 
    attr_reader :name, :speed, :color, :owner
    
    def self.accelerateCar(car)
        Notifications.sendSpeedAlert car 
        self.new car.name, car.speed + 10, car.color
    end
    
end

myCar = Car.new "Toyota" 50 "black" current_user

Car.accelerateCar myCar

Currying

  • Pojedyncze aplikowanie argumentów do funkcji
add = -> (x, y) { x + y }

add.call(5, 10) => 15

-------------------------

# Currying

add = -> (x, y) { x + y }

createrAdder = add.curry

add5 = createAdder.call(5)

add5.call(10) => 15

Kompozycja funkcji

class Car 
    attr_accessor :name, :speed, :color

    def accelerate
        @speed += 10
    end

    def slow_down
        @speed -= 10
    end

    def stop
        @speed = 0
    end
end

myCar = Car.new "Toyota" 50 "black"
myCar.accelerate
     .accelerate
     .slowDown
     .accelerate
     .stop

Kompozycja funkcji

  • Łączenie funkcji przekazując wynik wykonania jednej jako argument kolejnej
  • Skomplikowane rzeczy tworzymy dzieląc je na mniejsze problemy
def add5(x)
    x + 5
end

def multiplyBy10(x)
    x * 10
end

multiplyBy10( add5 3 ) === compose(multiplyBy10, add5).call(3)

Kompozycja funkcji

# String -> List[String]
getWords = -> (str) { str.split(' ') }


# List[String] -> Int
listLength = -> (list) { list.length } 


# String -> Int
getWordsCount = compose(
    listLength,
    getWords
)

getWordsCount.call("hello LRUG") => 2
  • Brak funkcji compose w Ruby, jest ona jednak łatwa do zaimplementowania samemu ( 1 linijka)

Wady FP

  • Początkowo większa czasochłonność w wykonywaniu zadań
  • Wydajność (jeśli język nie jest funkcyjny)
    • Duża alokacja pamięci
  • Zbyt restrykcyjne trzymanie się zasad może w efekcie skomplikować kod
  • Odrzucenie reguł powszechnie przyjętych przez community może utrudnić wejście w projekt nowym osobom

Dziękuję za uwagę

Made with Slides.com