functional programming in ruby

41
Functional Programming in Ruby Koen Handekyn CEO

Upload: koen-handekyn

Post on 12-May-2015

535 views

Category:

Technology


9 download

DESCRIPTION

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

TRANSCRIPT

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