Skip to content

Instantly share code, notes, and snippets.

@plexus
Created November 28, 2013 17:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plexus/7695852 to your computer and use it in GitHub Desktop.
Save plexus/7695852 to your computer and use it in GitHub Desktop.
############
#############
#### #####
#####
####
#### 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
@404pnf
Copy link

404pnf commented Dec 2, 2013

WOW!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment