functional programming in ruby

Post on 12-May-2015

535 Views

Category:

Technology

9 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

Functional Programming in RubyKoen HandekynCEO

Learning FP

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

but rewarding

it’s fun & productive

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, ...

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)

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)

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!

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

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]

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 ...

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

Taking a look at <<Enumerable >>

First introduce Closure

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

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

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

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

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

inject & reduce & foldl & foldr

foldl & foldr

Enumerable#reduce

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

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

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

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

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 ...

(: take a breath :)

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

Constants are Functions

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

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

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

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

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

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

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

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

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

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

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

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

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 }

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

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 ?

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]

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)

Merci

top related