Functional programming in ruby

41
Functional Programming in Ruby Koen Handekyn CEO

description

What the title says. Presented at Timisoara Ruby Community Event. Hosten by UnifiedPost Romania.

Transcript of Functional programming in ruby

Page 1: Functional programming in ruby

Functional Programming in RubyKoen HandekynCEO

Page 2: Functional programming in ruby

Learning FP

it’s (sometimes) (a bit) difficult (in the beginning)- you need to retrain your brain -

but rewarding

it’s fun & productive

Page 3: Functional programming in ruby

History

‣ Lambda calculus: 1930’s (Alonzo Church)

‣ Lisp: 1950’s, multi-paradigm language strongly inspired by lambda-calculus (symbolic manipulation, rewriting)

‣ML-family: 1970’s, general purpose functional programming language with type inference

‣Haskell: 1987, 1998, 2010, open standard for functional programming research

‣Other: Clean, F#, Scheme, Scala, Clojure, XSLT, Erlang, SQL, ...

Page 4: Functional programming in ruby

Isn’t all programming functional?

‣ FP proceeds from a startling premise—that we construct programs using only pure functions, or functions that avoid side effects like changing variables(state), writing to a database or reading from a file.

‣ In a pure functional programming language, everything is a function. we can pass them around and “calculate” with them (combine, evaluate or partially evaluate, etc).

‣A function defines a mapping from a “Domain” into the “Codomain” (image, range)

Page 5: Functional programming in ruby

Advantages

‣No mutable data and hence:

‣No side effects, no implicit hidden state, less bugs.

‣No variables ! (only values) => optimizations

‣Can (more easily) be parallelized over cpu’s (multicore), etc. => no locks, no race conditions, no deadlocks

‣ Enables Provability (both for humans and computers)

Page 6: Functional programming in ruby

FP vs OO

‣Whereas an object-oriented mindset will foster the approach of defining an application domain as a set of nouns (classes) [ person, ticket, etc ]

‣ The functional mind will see the solution as the composition or verbs (functions) [ register, sell ]

‣ Though both programmers may in all likelihood generate equivalent results, the functional solution will be more succinct, understandable, and reusable. Grand claims indeed!

Page 7: Functional programming in ruby

Immutable Objects

‣An OO pattern that actually originates in FP world

‣ ISO changing a data structure, don’t modify in place but create a new object.

‣ In Ruby this is typically the default. Methods that don’t follow this principle are assumed ‘dangerous’ and are typically marked with a ‘!’• name.reverse => returns a new string that contains the reversed name

• name.reverse! => replaces the name with the reversed value

Page 8: Functional programming in ruby

Ruby and FP

‣ Ruby is an imperative and OO language with closure support

‣ But we can apply (some) FP principles

‣ It allows to mix and match OO with FP programming style

‣ You can’t assume immutability ... it’s a choice

‣No (real) pattern matching (yet)

‣No lazy evaluation (yet)

A bit of pattern matching • x, *xs = [1,2,3,4]

x => 1xs => [2,3,4]

• a,b,tail = [1,2,3,4]a => 1b => 2tail => [3,4]

Page 9: Functional programming in ruby

Recursion# fibonacci functional through recursion

def fib(count, a = 1, b = 1 , r = []) count == 0 ? r : fib(count-1, b, a+b, r << a)end

fib(10) # => [1,1,2,3,5,...

r is the accumulator

needs a bit of practice but once you get it ...

Page 10: Functional programming in ruby

or also - As opposed to ?

‣Another look at it is to compare imperative programming languages with declarative programming languages

‣ Imperative = emphasize on how something is computed

‣Declarative = emphasize on what is to be computed and not on how

‣ Imperative is counterintuitive when you’re used to imperative programming

Page 11: Functional programming in ruby

Taking a look at <<Enumerable >>

Page 12: Functional programming in ruby

First introduce Closure

‣Closure = an anonymous function block together with a referencing environment

‣Where? javascript, python, ruby, PHP, C# 2.0, java 8! :)

Page 13: Functional programming in ruby

Enumerable#select

(1..10).select { |x| x.odd? } => [1, 3, 5, 7, 9]

# imperative styleodds = [](1..10).each do |n| odds << n if n.odd?end

Page 14: Functional programming in ruby

Enumerable#partition

(1..10).partition { |x| x.odd? } => [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]

# imperative stylep = [[],[]](1..10).each do |n| p[0] << n if n.odd? p[1] << n unless n.odd?end

Page 15: Functional programming in ruby

Enumerable#map

(1..10).map { |x| x * 2 } => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

# imperative styledoubles = [](1..10).each do |n| doubles << n*2end

Page 16: Functional programming in ruby

inject & reduce & foldl & foldr

Page 17: Functional programming in ruby

foldl & foldr

Page 18: Functional programming in ruby

Enumerable#reduce

(1..10).reduce { |x,y| x + y } # repeat sum=> 55

# imperative stylesum = 0(1..10).each do |n| sum += nendsum # => 55

Page 19: Functional programming in ruby

Enumerable#reduce

# repeat sum, start with 0(1..10).reduce(0) { |x,y| x + y }=> 55

# repeat multiply, start with 1(1..10).reduce(1) { |x,y| x * y }=> 3628800

# or ‘for real’ :)(1..10).inject(:+) => 55(1..10).inject(:*) => 3628800

Page 20: Functional programming in ruby

Enumerator#group_by

(1..6).group_by { |i| i%3 }=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}

# imperative stylep = {}(1..6).each do |n| k = n%3 p[k] ||= [] p[k] << n end

Page 21: Functional programming in ruby

Enumerable#sort

%w(rhea kea flea).sort #=> ["flea", "kea", "rhea"]

(1..10).sort {|a,b| b <=> a} #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

# imperative style# ... have fun ...

Page 22: Functional programming in ruby

(: take a breath :)

Page 24: Functional programming in ruby

Functions as Values in Ruby

inc = lambda { |x| x + 1 }inc = ->(x) { x + 1 }inc.(4) => 5

add = lambda { |x,y| x + y }add = ->(x,y) { x + y }add.(2,3) => 5

Page 25: Functional programming in ruby

Constants are Functions

constant = ->(c,x) { c }.curry

hello = constant.(“hello”)hello.(1) => “hello”hello.(“eugen”) => “hello”

Page 26: Functional programming in ruby

Composition

identity = ->(x) { x } # a closurepower = ->(base, x) { base**x }.curry

sum_of = ->(f, xs, i=0) { xs.inject(i) { |a,i| a+=f.(i) } }.curry

sum_of_numbers = sum_of.(identity)sum_of_power2s = sum_of.(power.(2))sum_of_squares = sum_of.(square)

# usagesum_of_numbers.(0..10) # => 55sum_of_squares.(0..10) # => 2047 sum_of.( power.(3) ).(0..10) # => 88573

a Higher Order function takes a function as parameter

Page 27: Functional programming in ruby

Constants are Functions

constant = ->(c,x) { c }.currysum_of = ->(f, r, i=0) { r.inject(i) { |a,i| a+=f.(i) } }.curry

word_count = sum_of.( constant.(1) )

word_count.( %w(welcome to the world of fp) ) => 6

Page 28: Functional programming in ruby

Currying and Partial Evaluation

power = ->(base, x) { base**x }.currypower.(10,2) # => 100power.(10).(2) # => 100

power2 = power.(2) # partial evaluationpower2.(3) # => 8

add = ->(x,y) { x + y }.curryadd.(2,3) => 5

inc = add.(1) # partial evaluationinc.(3) => 4

Page 29: Functional programming in ruby

Currying and Partial Evaluation

# meta functional programming ;)send = ->(m, o) { o.send(m) }.currylength_of = send.(:length)

length_of.(“koen”) # => 4length_of.([12,4,25,32,[2,2]]) # => 5

Page 30: Functional programming in ruby

More Composition

# from previouslength_of = ->(o) { o.length } sum_of = ->(f, r, i=0) { r.inject(i) { |a,i| a+=f.(i) } }.curry

# composetotal_length_off = sum_of.(length_of)

# usetotal_length_off.( ['koen','ciprian','eugen'] ) # => 16total_length_off.( [ [2,3,4], "koen", [3,4,5] ] ) # => 10

Page 31: Functional programming in ruby

Playing with boxes

box1 = { width: 230, heigth: 304 }box2 = { width: 340, heigth: 243 }

by_key = ->(k, o) { o[k] }.curryby_width = by_key.(:width)

taller = ->(f, a, b) { f.(a) > f.(b) }.curry

taller.(by_width, box1, box2) # => falsetaller.(by_key.(:heigth)).(box1,box2) # => true

Page 32: Functional programming in ruby

More boxes

compose = ->(f,g,x) { g.(f.(x)) }.currysquare = ->(x) { x*x }

square_width = compose.(by_width).(square)square_width.(box1) # => 52900

square_height = compose.(by_key.(:heigth)).(square)square_height.(box1) # => 92416

Page 33: Functional programming in ruby

More compositionmap = ->(f, a) { a.map { |x| f.(x) }}.curry # turn method into lambda

squares = ->(a) { map.(square).(a) }squares = map.(square) # nicer through composition, not ? :)

sum = ->(a) { a.inject(0,:+) }square_of_sum = compose.(sum).(square)

after = composesum_of_squares = after.(squares).(sum)

square_of_sum.([2,3]) # => 25sum_of_squares.([2,3]) # => 13square_of_sum.([2,3,4]) # => 81sum_of_squares.([2,3,4]) # => 29

Page 34: Functional programming in ruby

More compositionbook = [ %w(this is a long sentence), %w(this is a short), %w(yes) ]

foldl = ->(f, arr) { arr.inject { |r, x| f.(r, x) } }.curry

add = ->(a,b) { a+b }div = ->(a,b) { a*1.0/b }length = ->(x) { x.length }

sum = foldl.(add)divide = foldl.(div)

pair = parallel = ->(f,g,x) { [f.(x), g.(x) ] }.curry

average = ->(a) { sum.(a) / length.(a) }average = after.( pair.(sum).(length) ).(divide)

average_wordcount = after.( map.(length) ).(average) # => 3.33

Page 35: Functional programming in ruby

More Composition

book = [ %w(this is a long sentence), %w(this is a short), %w(yes) ]

flatten = ->(arr) { arr.flatten } # convert to lambda

wordlengths = after.( flatten ).( map.(length) )average_wordlength = after.(wordlengths).(average)

average_wordlength.(book) # => 3.4

Page 36: Functional programming in ruby

Liquer Stores

liquer_stores = []liquer_stores << { name: "total", d: 2.0, price: 32.0 }liquer_stores << { name: "shell", d: 2.6, price: 28.5 }liquer_stores << { name: "esso", d: 3.2, price: 41.0 }liquer_stores << { name: "q8", d: 3.5, price: 22.0 }liquer_stores << { name: "shell", d: 4.5, price: 19.0 }liquer_stores << { name: "q8", d: 5.5, price: 18.0 }

Page 37: Functional programming in ruby

Imperative Liquer

def cheap_boose_nearby (liquer_stores) min = liquer_stores[0][:price] liquer_stores.each do |store| if store[:d] < 5.0 then price = store[:price] price = price * 0.9 if store[:name] == "shell" min = price if price < min end end minend

Page 38: Functional programming in ruby

Declarative Liquer

def expensive_boose_nearby (liquer_stores) nearby = ->(d, x) { x[:d] < d }.curry near = nearby.(5.0) myPrice = ->(x) { x[:name] == "shell" ? x[:price]*0.9 : x[:price] } liquer_stores. find_all(&near). collect(&myPrice). maxend

recognize SQL ?

Page 39: Functional programming in ruby

Generators / Sequence / Infinite ...

# Functions that return a sequence of values# Here: fibonacci as infinite yielder

fibonacci = Enumerator.new do |list| a = b = 1 loop { list.yield a; a,b = b,a+b } end

fibonacci.take(10) # [1, .. , 55]fibonacci.take(15) # [1, .. , 377, 610]

Page 40: Functional programming in ruby

Enumerable as Class

class Fibs include Enumerable def each a = b = 1; loop { yield a; a,b = b,a+b }; endend

Fibs.new.take(10)

Page 41: Functional programming in ruby

Merci