Functional programming in ruby
-
Upload
koen-handekyn -
Category
Technology
-
view
525 -
download
9
description
Transcript of Functional programming in ruby
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 :)
Currying
‣ In mathematics and computer science, currying is the technique of transforming a function that takes multiple arguments (or a tuple of arguments) in such a way that it can be called as a chain of functions, each with a single argument (partial application). It was originated by Moses Schönfinkel and later re-discovered by Haskell Curry.
‣ curry(int, int => bool) = int => (int => bool)
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