In Ruby, there is a reusable piece of code called a block. It is designated by curly braces: {}
Many functions such as sort
, each
and times
allow you to specify a code block to execute.
For example, in Ruby, if you wanted to print "Hello" 5 times, you could do:
# The code inside the curly braces is a block that is passed to the function 'times'
5.times { puts "Hello" }
If you want to pass in a code block as a valid parameter to a function, you need to specify to Ruby that it is a function/code block by using the &
prefix. See below:
IMPORTANT: Like the splat arguments, this needs to be defined as a parameter of the function last
# Note the '&' before the 'func' argument in the method definition
def foo(&func)
func.call
end
foo { puts "Hello World" }
Not exactly. Blocks look VERY similar at a first glance but an anonymous function is more like a Ruby lambda function
Ruby is built on an "everything is an object" philosophy and that ironically has a few exceptions, one of which is that a block is NOT an object.
So how do we reuse blocks? We give it a name but a name can only be given to an object. This is where proc
comes in. It is a container object that holds blocks.
To declare a proc
, you can use Proc.new
.
To reference a proc inside a function, you use the &
prefix with the name of the proc. When the prefix is attached, Ruby essentially unpacks/unwraps the block and passes it to the function.
See below:
times_2 = Proc.new { |num| num * 2 } #define the proc
[1,2,3].map(×_2) #note how the proc is prefixed with the '&'
# => [2,4,6]
To call procs directly, use the call
method like so:
hello = Proc.new { puts 'Hello World' }
hello.call # => Hello World
WARNING: using return
inside a proc will cause it to not only exit out of the proc but return outside of the calling function. See below:
def foo
some_proc.call #The return in this proc will kick us out of function foo
return 'Goodbye' #This never gets executed :'(
end
some_proc = Proc.new { return 'Hello' }
foo
So:
'Goodbye' #Expected
'Hello' #Actual :(
In Ruby, there is a bit of shorthand notations which will, for most JS developers not familair with Ruby, look like black magic.
Converting symbols to Procs is one such example. See this example below:
["1", "2", "3"].map(&:to_i) # => [1,2,3]
Most devs will probably be like...
Everything is fairly normal until we hit the &:to_i
What is happening is:
to_i
is a function and when combined with:
gets turned into a symbol referencing theto_i
function (which just converts the object to an integer).- Recall that the
&
prefix for a proc usually unwraps/unpacks the block for a function to consume. In this case, since it's a symbol and not a block, Ruby calls theto_proc
function to convert it to a block. - This newly formed block is fed into the map function.
So...:
["1", "2", "3"].map(&:to_i) # => [1,2,3]
["1", "2", "3"].map { |arg| arg.to_i } # same as the above statement
NOTE: This notation and referencing the method via &
works with lambda
(see next section) too.
A lambda is a proc
but with a few small additions. It is declared almost the same way as a proc.
foo = Proc.new { puts "Hello World" }
bar = lambda { puts "Goodbye Cruel World" }
Nope. Lambdas and procs have 2 major differences:
- Lambdas check the number of arguments passed to it while procs do not. This means if a lambda has 3 arguments and is passed 2, it throws an error. Procs on the other hand just assign
nil
to any missing arguments. - Unlike procs, when a return statement is hit inside a lambda, it only exits out of the lambda and not out of the calling method.
foo = lambda { |x, y| x + y }
def bar
foo.call(5) #Insufficient args so Ruby throws an 'ArgumentError'
return 'Hello'
end
bar
foo = lambda { |x,y| return x + y } #The return only exits out of lambda, not calling method
def bar
foo.call(5, 6)
return 'Hello'
end
bar # Returns 'Hello' not 11
If a lambda is defined like so:
foo = lambda { |x,y| x + y }
Then invoking it like below will throw an error:
foo(5) # => NoMethodError: undefined method `foo` for Object
This is better explained at StackOverflow here
To pass procs/lambdas as parameters to functions for actions such as filtering, use the &
to refer to the proc/lambda. This essentially unpackages/unwraps the code block from the proc/lambda
stuff = [:hello, :goodbye, 'Hello', 'Goodbye']
is_symbol = Proc.new { |val| val.is_a? Symbol } #Lets filter out anything that isn't a symbol
stuff.select!(&is_symbol) # => [:hello, :goodbye]
To define a function, the def
keyword is used and is wrapped with an end
to finish off the code block.
For example:
#Function that takes in no args
def foo
puts 'Hello World'
end
or:
#function that takes in 1 argument
def foo(name)
puts "Hello #{name}!"
end
If you want to return something, you can use the return
keyword. HOWEVER, if the last line of a function is the return statement, then the return
keyword isn't needed. Ruby will automatically return the last variable set or retrieved without the return keyword. See below:
#bad
def foo
name = 'hello'
return name #unnecessary return
end
#good way to do it
def foo
name = 'hello' #Ruby will auto return this variable value.
end
Another example:
def times_2(n):
n * 2 #Ruby will auto return this value without the return statement.
end
A return
IS needed if this is NOT the last line of the function
def foo(x):
return "hello" if x == "friend"
return "hater!" if x == "hater"
"stranger"
end
In Java, you would define an arbitrary number of arguments of the same type via the ...
keyword like so:
// In Java
public void function(int ...numbers) { /* do something */ }
In Ruby, the splat arguments keyword *
is used. This tells ruby that we don't know how many arguments there are. We just know there is at least one
For example:
def todd_five(greeting, *bros)
bros.each { |bro| puts "#{greeting}, #{bro}!" }
end
todd_five("Innuendo Five!", "J.D", "Turk")
In JavaScript, it is mandatory to execute function with no arguments with parentheses but in ruby, parentheses aren't required and so most devs don't use them:
SomeClass.foo #This is equivalent to SomeClass.foo() in JS
Unlike JS, in Ruby, parameters don't need to be enclosed by parentheses. The Ruby intepreter just looks for the parameters to be seperated by commas.
puts 'Hello' 'World' 'Goodbye' # Similar to console.log('Hello', 'World', 'Goodbye')
puts('Hello', 'World') # Also correct in Ruby but less used as its unneccessary
See the Coding Conventions page for more details.
tl;dr: Omit parentheses for anything with keyword status in Ruby and explicitely use parentheses for everything else.
In Ruby, functions can be passed blocks via {} which act similar to anonymous functions in JavaScript (see below)
books = {}
books['stuff'] = 'hello' #setting a key and value is the same as in JS
books.values.each { puts 'Hello' } #equivalent to Object.values(books).forEach(function(){ console.log('Hello') }); in JS
Some JS functions do their changes to an object in place and some don't. Ruby, however, makes this matter very clear.
Executing the function and ending with !
makes the change in-place. For example:
name = "Gautham"
puts name.upcase # => "GAUTHAM"
puts name # => "Gautham" (the name wasn't changed)
puts name.upcase! # => "GAUTHAM"
puts name # => "GAUTHAM" (the name was changed when ! was specified in prev statement)
For functions with parameters, the !
comes before the parameters
str = "sea shells by the sea shore"
str.gsub!(/s/, "th") # Note how the ! is before the parameters
print str # Returns "thea thhellth by the thea thhore"
Note that you cannot use the '!' to try to do an inplace change if the original type and resulting type from the operation are different.
str = 'Hello'
str.to_i! #This will return an error. String and integer are not the same type
str = str.to_i #The workaround
In Ruby, parameters to anonymous functions are enclosed via |
or "pipe" characters like so:
books.values.each { |book| puts 'Gotta read ' + book }
which is equivalent to in JavaScript:
Object.values(books).forEach(function(book) {
console.log('Gotta read ' + book);
});
In Ruby, you can yield control to run a block of code inside another function. This has a variety of uses.
As a refresher, a block is just a reusable piece of code enclosed by braces {}
Using the yield
keyword you can invoke code blocks and pass it arguments as if they were functions. Unless the blocks are specified as arguments, they are outside the parentheses. See below:
def foo(greeting)
puts 'Inside the function foo'
yield(greeting)
puts 'Back inside the function foo'
end
foo("Top of the mornin to ya") { |arg| puts "#{arg} John" }
This prints out:
Inside the function foo
Top of the mornin to ya John
Back inside the function foo