Ruby's && PHP's take on Named & Anonymous Functions
This is arguably one of the most important concepts in any programming lanuage. Storing and retrieving data is incredibly useful.
Try to imagine building any type of simple information system without being able to change the data. It’s like trying to box with one hand tied behind your back.
Functions are how we define actions. Without the ability to change or react data, then applications would be severly limited in what they could do.
So, long story short, functions are a pretty cool concept and I’ll show you the different flavors you’ll have at your disposal in Ruby:
Named Functions
In PHP we’re no stranger to functions. I’m not just talking about the given functions built into the language like isset
, is_null
, array_map
or count
.
I mean custom defined functions you’ve created in your application.
I’ll take probably the most famous function defined in Laravel:
No namespace, no object instantiation required. Simple.
So even though everything in Ruby is an object, it doesn’t mean you have to always instantiate an object to do little things.
# die and dump literally anything
#
# @param Mixed
def dd(anything)
abort(anything)
end
dd('Halt plz.')
=> 'Halt plz.'
Now, Rails developers are going to cringe at this function. You’ll never actually die and dump to debug in Rails or Ruby applications. Like ever. I’ll cover that in the debugging section.
So yea, named functions in either language are super similar to each others. The biggest difference is that Ruby just needs the keyword def
to define the start of a function whereas PHP wants you to type the whole word function
. w00t!
Passing Arguments
Yup, this topic actually deserves a whole section. In PHP this is a moot issue. You have only a few different ways of passing arguments. The biggest difference is how arbitary $options are passed.
You may recognize this pattern, where the last argument for a function is a way of passing an associative array that alters the behavior of the function:
Mmm ice cream with peanut butter. Don’t knock it till you try it.
This pattern is used quite a bit in Ruby, but they had a little twist. When calling a function, you don’t have to explictly pass the last argument with the {}
that usually denote a Hash. It’s optional to provide them.
Here’s an example with our yummy combine example from above but Rubified:
def combine(cookie, ice_cream, options = {})
dessert = "#{cookie} and #{ice_cream}"
# if a topping is included, let's add it to the dessert.
dessert = "#{dessert} with #{options[:topping]}" if options.key?(:topping)
end
dessert = combine('oreo', 'vanilla ice cream')
puts dessert
=> 'oreo and vanilla ice cream'
Now check out how we pass the options Hash - no wrapping {}
brackets required!
better_dessert = combine('oreo',
'vanilla ice cream',
topping: 'peanut butter')
puts better_dessert
=> 'oreo and vanilla ice cream topped with peanut butter'
When you pass a Hash without the wrapping curly brackets {}
as the last argument, Ruby knows what’re trying to pass to the argument and automatically populates the last variable (a.k.a. options in our example above) with that Hash.
Imagine that our combine function could accept many options, the Hash argument still works the same:
dessert = combine('ginger bread cookies',
'pumpkin ice cream',
topping: 'butterscotch chips',
drizzle: 'caramel'
)
=> puts dessert
'ginger bread cookies and pumpkin ice cream topped with butterscotch chips drizzled with caramel'
Now you have yourself a nice little marketing copy generator for ice cream stops.
Before we move on , I have one last trick to you show that boogled my mind when I first started using Ruby.
Ruby tries really hard to make your life easier as a developer. Including calling arguments.
dessert = combine 'cookies', 'ice cream', topping: 'choco chips', drizzle: 'caramel'
Yea, the outer paranthesis are totally optional. This is absolutely foreign at first, and like me, you may be wondering how the heck Ruby can interpret where the arguments begin and end.
However this inference will quickly become second nature. Reading or writing in this format is optional, but it’s very easy to pick up on when it works and reads nicely.
Anonymous Functions a.k.a. Closures
Thanks to the addition to Anonymous Functions in PHP, we have the ability to start doing things like Higher Order Functions - you can see this pattern in action in GuzzleHttp’s Guzzle libary and Laravel’s Router module. That’s an advanced design pattern that’s outside the scope of this book, but just know it’s possible to use this pattern in both languages.
PHP can execute anonymous functions stored in a variable:
I shouldn’t probably tell you this. But since version 7, PHP can actually execute anonymous functions immediately just like Javascript:
Not that you couldn’t do it in PHP 5.6 and below, it just needed to be called with user_call_func
:
You decide what’s more legible.
So with Ruby’s “Everything is an Object” mantra - can it still do Closures? You bet.
And before we go further,you should know that Ruby uses Closures quite a bit. In PHP you can get by without using Closures for a long long time, but in Ruby they’re a pretty central concept. Don’t sweat it, they’re going to feel natural in no time.
Two Ways to Tango
In our little PHP world, anonymous functions can only really be written out in one way. They need the keyword function, a little set of parenthesis ()
to define your arguments, and last but not least - a wrapping set of curly brackets {}
to hold the business logic in place:
This formula holds true even if you’re placing your function in a Class or just dangling out there in the Global namespace.
However Ruby is a bit more flexible than that. You can type a function “block” in multiple ways. Here’s the most similar to PHP, the block that takes up multiple lines:
do |argument|
return argument + 1
end
First big difference is the total lack of curly brackets {}
in this Ruby example. They happen here and there in Ruby but not very often. We’re used to wrapping functions with {} in PHP, but in Ruby we instead infer the opening bracket { and use the keyword end as an alias to the end bracket }
.
As a result of this inference on Ruby’s part, we save ourself 1 keystroke every method we write. Lovely.
Second, but minor difference is the lack of paranthesis ()
that denote the expected arguments, i.e. ($argument
). We instead wrap the expected arguments with pipes ||
. So ($argument)
becomes |argument|
.
P.S. don’t forget to say goodbye to our good friend $
.
One more goodie Ruby provides for us. There is also special syntax for blocks that’s easier to read for 1 line functions:
{ |argument| return argument + 1 }
Which is exactly the same as saying:
do |argument|
return argument + 1
end
but with just 1 line.
Inferred Return
How many times have you made this mistake in PHP? Can you spot it?
What?
Uppercased dog breed names should be printed. Instead they were replaced with NULL
.
All because we forgot to include a return statement inside of our array_map
function. As it was iterating over each dog, the dog’s name was uppercased but it was never stored.
What if functions just assumed that the last variable you reference is the result? Wouldn’t these mistakes happen a lot less and type less too?
The answer is yes. Both of these problems are solved by Ruby’s inferred return.
Ruby is smart enough to let the last defined varible be the return value:
dogs = ['jack russell', 'collie', 'german shepard'].map do |dog|
# no `return` required!
dog.upper
end
dogs.each do |dog|
puts dog
end
=> 'JACK RUSSELL'
=> 'COLLIE'
=> 'GERMAN SHEPARD'
Boom. Easy as pie.