Created
November 28, 2013 17:51
-
-
Save plexus/7695852 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
############ | |
############# | |
#### ##### | |
##### | |
#### | |
#### Functional Programming | |
##### | |
####### in Ruby | |
######### | |
########## | |
####### #### | |
###### ##### A lightning talk | |
###### ##### | |
####### ##### by @plexus | |
####### #### | |
####### #### for @RubyConfAr | |
####### ##### | |
####### ##### | |
############# ########### | |
############# ########### | |
######################################## | |
# | |
# Functional programming | |
# | |
######################################## | |
# | |
# 1. Restrictions | |
# | |
# Immutability | |
x = 'Ola'.freeze | |
y = 7 | |
z = true | |
# Pure functions | |
def square(x) | |
x * x | |
end | |
# Not allowed | |
x << 's' | |
y += 1 | |
attr_writer / attr_accessor | |
# | |
# 2. Lambdas | |
# | |
a = 7 | |
fn = ->(b) { a + b } | |
fn.(3) # => 10 | |
# CONS CELLS | |
cons = ->(x,y) { | |
->(f) { | |
f.(x,y) | |
} | |
} | |
head = ->(c) { | |
c.(->(x,y) { | |
x | |
}) | |
} | |
tail = ->(c) { | |
c.(->(x,y) { | |
y | |
}) | |
} | |
list = cons.(1, cons.(3, cons.(7, cons.(9, nil)))) | |
head.(tail.(list)) # => 3 | |
nth = ->(list, n) { | |
n == 0 ? head.(list) : nth.(tail.(list), n-1) | |
} | |
nth.(list, 1) # => 3 | |
idx = nth.curry.(list) | |
[1,2,3].map(&idx) # => [3, 7, 9] | |
# | |
# 3. language support | |
# | |
# Enumerable / Enumerator / Proc | |
map, inject, curry | |
######################################## | |
# | |
# Value Objects | |
# | |
######################################## | |
class Bottle | |
attr_reader :size, :contents | |
def initialize(label, size, contents) | |
@label, @size, @contents = [label, size, contents].map(&:freeze) | |
end | |
def add(i) | |
self.class.new(@label, @size, @contents + i) | |
end | |
def inspect | |
"<#{@label} bottle, %.2f%%>" % (Float(@contents) / @size * 100) | |
end | |
end | |
######################################## | |
# | |
# Function objects | |
# | |
######################################## | |
class GoldenRatio | |
def call(num) | |
num * 1.618 | |
end | |
end | |
#%####################################### | |
# | |
# Let's have some fun | |
# | |
######################################## | |
class Class | |
def to_proc | |
->(*args) { new(*args) } | |
end | |
end | |
class Symbol | |
def call(*args) | |
->(obj){ obj.public_send(self, *args) } | |
end | |
end | |
class Proc | |
def &(other) | |
->(*args) { self.(*args) && other.(*args) } | |
end | |
end | |
numbers = ([ ->{rand(100)} ] * 100).map(&:call) | |
numbers.select(&:>.(50) & :<.(60)) # => [53, 52, 59, 53, 56, 56, 54, 55, 51, 58, 53, 57, 53] | |
#%####################################### | |
# | |
# Infinite lists | |
# | |
######################################## | |
Inf = (1..Float::INFINITY) | |
def squares | |
Inf.lazy.map { |i| i * i } | |
end | |
squares.take(20) # => #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>:take(20)> | |
squares.take(20).to_a # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400] | |
fizz = Inf.lazy.map { |i| ['Fizz',nil,nil][i%3] } | |
buzz = Inf.lazy.map { |i| ['Buzz',nil,nil,nil,nil][i%5] } | |
fizzbuzz = Inf.lazy.map do |i| | |
str = [fizz.next, buzz.next].join | |
if str.empty? | |
i | |
else | |
str | |
end | |
end | |
fizzbuzz.take(15).to_a # => [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"] | |
######################################## | |
# | |
# Hamster | |
# | |
######################################## | |
require 'hamster' | |
Hamster.vector(1,2,3,4) # => [1, 2, 3, 4] | |
Hamster.set(3,3,4) # => {3, 4} | |
Hamster.hash(name: 'Arne', likes: Hamster.list(:tea, :chocolate, :ruby)) # => {:name => "Arne", :likes => [:tea, :chocolate, :ruby]} | |
Hamster.stream { :hello }.take(3) # => [:hello, :hello, :hello] | |
Hamster.iterate(0) {|i| i.next}.take(5) # => [0, 1, 2, 3, 4] | |
######################################## | |
# | |
# Lambda Calculus Arithmetic | |
# | |
######################################## | |
# Integer arithmetic in the lambda calculus | |
# This is our only admonission, it turns a lambda calculus "number" into an | |
# actual interger, so we can inspect our results. For the rest we only use | |
# lambdas | |
R = ->(n) { n.(->(i) { i+1 }, 0) } | |
# Zero, i.e. apply a function to an argument zero times | |
_0 = ->(f,x) { x } | |
# Successor, i.e. n+1 | |
succ = ->(n) { ->(f,x) { f.(n.(f,x)) } } | |
_1 = succ.(_0) | |
_2 = succ.(_1) | |
_3 = succ.(_2) | |
_4 = succ.(_3) | |
R[_0] # => 0 | |
R[_3] # => 3 | |
R[_4] # => 4 | |
# Addition, apply f m times, then n times | |
add = ->(n, m) { ->(f,x) { n.(f, m.(f,x)) } } | |
R[add.(_2, _3)] # => 5 | |
# Predecessor, n - 1 | |
# Take a tuple (a=0, b=0) and turn it into (b, b+1), do that n times, so | |
# (0,0) => (0,1) => (1,2), etc | |
# After n times the first item of the tuple is n-1. | |
# Caveat : a tuple in this case is a lambda that applies a lambda to two arguments | |
pred = ->(n) { | |
->(f,x) { | |
n.( | |
->(tup) { | |
tup.(->(a,b) { ->(h) { h.(b, succ.(b)) } }) | |
}, | |
->(g){ g.(_0, _0) } | |
).(->(r, _) { r.(f,x) }) | |
} | |
} | |
R[pred.(_4)] # => 3 | |
min = ->(m,n) { | |
n.(pred, m) | |
} | |
R[min.(_4, _2)] # => 2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
WOW!