skip to content

Functional ruby

Having written elixir for a while I got curious to see what all can be done in ruby, in a functinoal way mostly in comparison to Elixir. Below are some of the ruby methodologies that I found. I am skipping lambdas and procs for now since they are widely discussed at many places. The snippets are self explanatory.

Splat * and destructuring a list

Never knew I could deconstruct an array like this.

first, *rest = [1,2,3]
# => first == 1
# => rest == [2,3]

The above led me to reimagine #map in a functional way.

arr = [1,2,3]
fn = ->(x) { |x| x*2 }
def remap(arr, fn)
return if arr == []
first, *rest = [1,2,3]
first, *rest = arr
[].push(fn.(first)).push(remap(rest, fn))
end

Shorthand proc invocation

Ruby has procs and lambdas, for this post we are not digging on those two topics much.

Creating a proc

double = Proc.new { |x| x*2 }

Creating a lambda

double = ->(x) { x*2 }
lambda = lambda { "Lambda" }

Back to the current section, you would know this:

def main
[1,2,3].map(&:+)
end

The above snippet retruns the sum of the elements.

Let’s decrypt (&:+)

& is shorthand for to_proc

def to_proc
proc { |obj, *args| obj.send(self, *args) }
end

: is just a symbol notation (Read on ruby symbols) + the operator

Now essnetially, you could look at it as & :+, so a symbol :+ is passed. Now, as per the definition of to_proc the symbol is called upon the object itself.

So if you pass a symbol, it is turned into a proc.

Symbol#to_proc was added to address this common pattern

arr.map do { |x| x.upcase }

This then becomes:

arr.map(&:upcase)

Now in order to create a custom functions that we can pass to map we should use a lambda. Lambdas are essentially anonymous functions.

If you are familiar with javascript, you would have used lot of anonymous functions for defining callbacks. On a similar anology we can proceed with a squaring function.

Terminal window
irb(main):004:0> [1,2,3].map(&(->(x) { x*x}))
=> [1, 4, 9]

Looks cryptic but you can see that we created an anonymous function and passed it in with &.

We could take it out and define the function somewhere else giving

Terminal window
irb(main):005:0> fn = ->(x) { x*x }
=> #<Proc:0x00007f89f19b3cf0@(irb):5 (lambda)>
irb(main):006:0> [1,2,3].map(&fn)
=> [1, 4, 9]

Let’s take a step back now, we just discussed & essentially calls #to_proc on it.

Another important thing to remember, if we use a symbol the corresponding method is called on the object itself i.e

Terminal window
irb(main):009:0> [1,2,3].map(&:fn)
Traceback (most recent call last):
5: from /Users/manu/.rbenv/versions/2.6.3/bin/irb:23:in '<main>'
4: from /Users/manu/.rbenv/versions/2.6.3/bin/irb:23:in 'load'
3: from /Users/manu/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in '<top (required)>'
2: from (irb):9
1: from (irb):9:in 'map'
NoMethodError (undefined method 'fn' for 1:Integer)

Since Integer doesn’t have a method fn, it failed.

Hence,

Terminal window
irb(main):011:0> [1,2,3].map(&:odd?)
=> [true, false, true]
irb(main):012:0>

That’s it!


Updated on