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.
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
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
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,
irb(main):011:0> [1,2,3].map(&:odd?)=> [true, false, true]irb(main):012:0>
That’s it!
Updated on