Ruby
(This page in particular I have spent some time writing.
I will be up-front and say I don't like Ruby, but the more I write about Ruby, the less I can find myself complaining about it.
The more I understand Ruby the more I'll update the information here.)
Ruby is an object-oriented (OO) scripting language.
Ruby was designed to be intelligible to humans and not just to machines.
Most languages use strict syntax so the computer will understand what its instructions are.
Ruby, on the other hand, uses English syntax to aid in readability and has numerous techniques to do the same thing to aid in ease of writing.
I am an advocate of removing unnecessary syntax: I don't like having a million ways to do something.
Because of that, I am not a fan of Ruby.
There is a lot to Ruby that I don't know and don't understand, but from what I know I don't particularly take to it.
That being said, I am not saying to not learn Ruby: quite the opposite.
Ruby is one of the languages everyone in programming should learn.
It is used in web development (
Ruby on Rails) and is available on most major platforms.
I tried it out of curiosity and don't like it, but that doesn't mean you won't like it either.
Features of Ruby
Before jumping into what I don't like about Ruby, I think it's only fair to write about some of the features the language offers.
Primarily I will focus on what the language has that most languages don't have.
Hashes, Classes, and Modulemobiles
I love Lua, and as such, I am used to everything being a table.
Ruby, however, takes a more Pythonic approach to various data types.
Ruby has hashes, classes, and modules, which parallel Python's dicts, classes, and classes (again).
Dictionaries in Python, hashes in Ruby, and tables in Lua are all similar: a set of key-value pairs.
Lua does not have a concept of classes, but Ruby and Python do.
The classes allow you to create innumerable instances of them and extend other classes.
Ruby takes things a step further by adding modules, which are essentially static classes or interfaces that can be implemented by any class.
Symbols
Ruby provides a special data type called a symbol.
In normal OO languages, objects are unique and multiple clones may not point to the same location.
For example, I ran the following two blocks of code separately.
In the first box you see the two object ids for the strings differ.
Even though the two strings are the same arrangement of characters and equate to each other, they refer to different objects.
Symbols, written like :symbol_name
, evaluate to themselves.
All symbols of the same name across a Ruby program have the exact same object id.
As such, they are convenient as keys in hashes and method names because of their uniqueness and immutability.
a = "This is a string"
b = "This is a string"
puts a.object_id #=> 74149330
puts b.object_id #=> 74149320
puts a == b #=> true
puts a.object_id == b.object_id #=> false
a = :symbol_name
b = :symbol_name
puts a.object_id #=> 146968
puts b.object_id #=> 146968
puts a == b #=> true
puts a.object_id == b.object_id #=> true
Sigils
Ruby also has what are called sigils.
In some languages, like Perl, sigils appear next to variable names to indicate the type of the value (in Perl
$
indicates the value is a scaler,
@
indicates it's an array, etc).
In Ruby, sigils have two types.
The first type of sigil prefixes the variable name and sets the scope of a variable.
There are five types of variables in Ruby, each defined by how the variable name starts.
On the left are how a variable can start and on the right are what type of variable it will be.
a-z or _ |
local variable |
A-Z |
constant |
$ |
global variable |
@@ |
class variable |
@ |
instance variable |
Anything else at the front of a variable name would not be allowed as a variable name.
The second type of sigil suffixes a method name to indicate the return value of a method.
One sigil that I wished was in Lua is the query sigil ?
.
The query comes at the end of a method name to indicate it's a predicate (returns a boolean).
This makes the code look a bit more like English.
For example: "some string".includes? "t"
reads like "does 'some string' include 't'?", to which the answer is 'yes'.
Another sigil is the bang sigil !
.
The bang follows a method name to indicate the method is a "dangerous" method, one that affects the object in question and does not merely return a new object.
Take the following:
# no bang
a = [1, 4, 2, 5, 3]
b = a.sort
puts a.inspect #=> [1, 4, 2, 5, 3]
puts b.inspect #=> [1, 2, 3, 4, 5]
# with a bang
a = [1, 4, 2, 5, 3]
b = a.sort!
puts a.inspect #=> [1, 2, 3, 4, 5]
puts b.inspect #=> [1, 2, 3, 4, 5]
A Quick Note About Constants
Since I previously mentioned constants I would like to append some extra information about them.
Constants are called "constants" in various languages but their implementations differ.
In C, a constant variable is completely immutable: you cannot change where it points or any of the values bundled inside it.
To change the values inside a constant in C you would need to declare the variable as a constant pointer, which doesn't let you change where it points, but allows you to change the values inside.
In Java, constant variables (those declared final) act like constant pointers from C.
Ruby constants act like constant pointers, but Ruby doesn't enforce constant-ness.
You can actually change the references of constant variables; Ruby will warn you when you attempt to change a constant value, but will let you do it anyway.
Observe:
# Input.rb
# Set original value
A_CONST = "Hello"
puts A_CONST #=> Hello
# You can change the values inside the object
A_CONST[0] = "J"
puts A_CONST #=> Jello
# You shouldn't change the value of the variable
A_CONST = "Bob"
puts A_CONST #=> Input.rb:10: warning: already initialized constant A_CONST
#=> Bob
Blocks, Procs, and Lambdamobiles
There are lambdas, procs, and blocks, and each has its own differences.
All of these are Ruby's odd way of doing closures and passing functions around.
Blocks
Blocks are nameless pieces of code that are implicitly "passed" to certain functions.
You define a block after a method call and the method may yield to the block, sometimes passing values in.
This sort of functionality is very convenient as it gives module and API writers the ability to generalize their code and gives module and API users the ability to define what's going on.
Blocks are rather limited, however.
For one, you can provide no more than one block to a method call.
Blocks are also not objects and cannot be stored to a variable, but if you want to store them to variables, you can: that sounds contradictory, but read on.
Procs
Procs are objects with a customizable method that allows them to be explicitly or implicitly "passed" to certain functions.
They can be defined and set in two ways.
First, a proc can be set to a variable by using Proc.new
and providing a block, like square = Proc.new { |n| n ** 2 }
would create a Proc that, when called, returns the square of the passed number.
Second, they can be created implicitly by using blocks.
In a method, blocks can be either yielded to, or converted to a proc by preceding one of the parameters with &
.
The following pieces of code do the exact same thing.
# Using a block
class Array
def apply!
self.each_with_index do |n, i|
self[i] = yield(n)
end
end
end
result = [1,2,3,4].apply! do |n|
n ** 2
end
puts result.inspect #=> [1, 4, 9, 16]
# Converting block to proc
class Array
def apply!(&code)
self.each_with_index do |n, i|
self[i] = code.call(n)
end
end
end
result = [1,2,3,4].apply! do |n|
n ** 2
end
puts result.inspect #=> [1, 4, 9, 16]
# Passing a proc
class Array
def apply!(code)
self.each_with_index do |n, i|
self[i] = code.call(n)
end
end
end
result = [1,2,3,4].apply!(
Proc.new{ |n| n ** 2 }
)
puts result.inspect #=> [1, 4, 9, 16]
Procs have one more added benefit.
They act as if their code is injected directly into the code of its calling method, so any returns, breaks, exception raises, and other control flow keywords affect the calling method.
For example, a proc with an explicit return called inside a method will break out and return from the method without executing any following code in the method.
Lambdas
Lambdas are procs.
Technically speaking, lambda
is a method in Ruby that converts a block into a special type of proc, so lambdas are not a different class (puts (lambda {}).class #=> Proc
) but we still refer to these special procs as lambdas.
Lambdas differ from procs in only two respects.
First, lambdas check the number of arguments passed to them and will error when the number received and number expected differ.
Procs do not check and will assign nil
to unspecified arguments and will ignore extra passed arguments.
Second, lambdas called within a method will not affect the control flow of the method: an explicit return in a lambda called inside a method will not break out, whereas a proc would.
These differences are because lambdas act like regular methods but wrapped in an object.
Method Objects
In addition to blocks, procs, and lambdas, Ruby has the added capability of passing methods around using symbols.
In Ruby, all methods are called automatically, regardless of the presence of parens: both array.inspect
and array.inspect()
are valid method calls.
As such, one does not simply assign a method directly to a variable like they could in Lua or Python.
Instead, there is the built-in Ruby method method
which takes a symbol (the name of the method in question) and returns a Method object.
Method objects behave like regular methods, but wrapped in an object (same as lambdas): you call them with .call
, they complain about argument mismatch, and they don't break out of their calling methods.
You can even convert methods into blocks by prefixing the reference with an &
and placing them after a method call.
Using the code from above, the following are also legal, thus providing us with at least six ways to square every element in an array.
# Passing a Method object
class Array
def apply!(code)
self.each_with_index do |n, i|
self[i] = code.call(n)
end
end
end
def square(n)
n ** 2
end
result = [1,2,3,4].apply!(method(:square))
puts result.inspect #=> [1, 4, 9, 16]
# Converting Method object to block
class Array
def apply!
self.each_with_index do |n, i|
self[i] = yield(n)
end
end
end
def square(n)
n ** 2
end
result = [1,2,3,4].apply! &method(:square)
puts result.inspect #=> [1, 4, 9, 16]
# Converting Method object to block to proc
class Array
def apply!(&code)
self.each_with_index do |n, i|
self[i] = code.call(n)
end
end
end
def square(n)
n ** 2
end
result = [1,2,3,4].apply! &method(:square)
puts result.inspect #=> [1, 4, 9, 16]
Why Ruby Isn't my Cup of Tea
If Python and Ruby were real humans, they would be bitter rivals: Python prides itself on being concise and having one way to do something; Ruby prides itself on being as redundant as it can possibly be.
Like I said, I don't like having a million ways to do something; I like things to be a bit less chaotic.
It seems like Matz (creator of Ruby) said to himself "I wonder if this should be put in" and never said no: he was so preoccupied with whether or not he could that he didn't stop to think if he should.
There are many instances in Ruby where there are various means to achieve the same thing.
It isn't that big of an issue to have multiple ways to do something: in fact, having all these different options gives a better chance that it is possible to do whatever you want to do.
The issue for me is that all these redundancies lead to inconsistent code, which in turn affects comprehensibility.
Matz's Third Law:
for every control-flow, there is an equal and opposite re-control-flow
Just like any language Ruby has the if ...
statement, but there is its complement unless ...
which the same as if not ...
.
There is while ...
and also until ...
, identical to while not ...
.
For these, I can sort of understand why there are the complements.
Ruby is designed for readability, and sometimes it is easier for some to say "unless you're tired, run" instead of "if you're not tired, run".
I'd still opt for the latter.
Optional and Assumed Syntax
There aren't many languages with completely optional syntax.
In Lua, you have the option of following your statements with a semicolon, which is used for disambiguation.
It's required in some instances when the compiler has the possibility of getting confused with what you've written.
In JavaScript, the semicolon at the end of statements is also somewhat optional.
When parsing through the script, if JS doesn't find a semicolon where it expects one, it will imagine one is there and continue through.
Include your semicolons anyway.
Ruby is the only language that I can think of where certain keywords are completely optional.
For one, every method is automatically called with all following comma separated values (up to a newline) passed as arguments, so the parentheses are not required.
The parentheses are also not required for method definitions.
In fact, parentheses are completely optional: they are needed only for clarification for the interpreter, like for specifying the order of arithmetic operations.
The keywords do
and then
are optional after while
, until
, if
, and unless
.
The return
keyword is also optional.
Ruby automatically returns the last statement it evaluates, so a legal function is def square(n) n ** 2 end
.
The only exception to return
being optional is in a proc, where an explicit return jumps out of the calling method.
That instance, however, is not really an exception since a proc call acts as if the code was written directly into the method.
Too Many Loops
Just a
while
or infinite loop you can break out of is enough.
Most languages offer other loops (like
for
and
repeat ... until
) as shorthand code, which is convenient; but Ruby must have more.
(array/hash).each
, (array).each[_with_index|_index]
, and (hash).each[_pair|_key|_value]
for iterating over every element of an enumerable
(int).times
or (int).upto(int)
for iterating a definitive number of times
for
for over every element of an enumerable or iterating a definitive number of times
loop
for iterating forever
while
for iterating as determined now
until
for iterating as determined later