Weird Ruby
February 14, 2019 - @kddeisz
Recently, I wrote a plugin for prettier
for the Ruby programming language. Over the course of that process, I discovered a lot of eccentricities of Ruby (by needing to account for each node type of, as well as each variant in structure of, the AST). I found some fun things, and so in the spirit of Gary Bernhardt’s “wat” talk, I’m going to share them with you here.
case
case
expressions in Ruby are much like switch statements in other languages. They provide a way to branch logic based on one common value. However, you can also have case
expressions without a predicate at all, which makes them effectively if..elsif
chains, as in:
case
when foo == 1
...
when bar == 2
...
end
This is functionally equivalent to checking if foo == 1
and elsif bar == 2
.
flip-flops
Flip-flops (the name coming from circuit design) have got to be one of the most esoteric operators in existence. This article does a great job explaining it. Here’s an example:
lines.each do |line|
next unless (line =~ start_pattern)..(line =~ end_pattern)
puts line
end
Basically, this will loop through some list lines
and will print out every line between the time that the first condition is met until the time that the second condition is met.
for loops
Did you know Ruby still has for
loops? I certainly didn’t. They look like:
for num in [1, 2, 3] do
puts num
end
This also means that in
is a reserved word, so you can’t use it as an identifier.
hooks
Ruby has two special blocks that are built into the language that allow you to hook into start and exit time of a script. I’d imagine they’re particularly useful for scripting and less so for application-level logic. They look like:
BEGIN {
puts 'script has started'
}
END {
puts 'script has ended'
}
Interestingly, these blocks don’t support do...end
syntax. I’m not really sure why.
numbers
In Ruby there is a special syntax for numbers in binary, octal, and hex. All of the following statements evaluate to true:
0b10 == 2 # this is a binary number
0o10 == 8 # this is an octal number
0x10 == 16 # this is a hex number
On top of that, you can even drop the o
for octal numbers entirely and just use a 0
prefix, i.e. 010 == 8
is true. This one could pretty easily bite you if you don’t watch out!
procs
Procs and lambdas both have a special syntax for being called using square brackets. As in:
add = ->(left, right) { left + right }
add[3, 4] # => 7
The number of arguments you pass into the square brackets aligns with the number that you pass into proc. So for procs or lambdas with an arity of zero, you can exclude arguments entirely, as in:
greet = -> { puts 'Hello, world!' }
greet[]
strings
Strings have all kinds of fun properties! Below are a couple of things by which I was surprised.
%x
literals
You probably know about backtick expressions (i.e., `ls`
) which will spawn a process, execute it in the shell, and then return the stdio output back to the caller. (Be careful when doing this and please don’t RCE yourself.)
You might not know about %x
literals, which are actually the same thing (i.e., %x[ls]
). Don’t get confused with the other %-literals like %w
and %i
that create arrays!
%q
literals
You can use %q
and %Q
to create string literals (e.g., %q[abc]
). These are effectively the same thing as using single quotes and double quotes, respectively, except that in the latter case you don’t need to escape your double quotes. Convenient.
Interpolation
Normal string interpolation in Ruby looks like "a #{b} c"
, where b
is some variable that responds to to_s
(i.e., everything except BasicObject
). But did you know that you can skip the braces if you’re interpolating an instance, class, or global variable? In other word, the following code is entirely valid:
@instance = 'instance'
@@class = 'class'
$global = 'global'
"#@instance #@@class #$global" == 'instance class global' # => true
It even works inside regex expressions! If you replace the double quotes with /
in the above expression you’ll get a regex with interpolated variables.
?
literals
I’ve saved the best for last, as this was truly out of left field. Both of the following statements is valid in ruby:
?a == 'a'
?\M-\C-a == "\x81"
As it turns out, the ?
character allows you to create strings of length 1, with a couple special extras thrown in. For further explanation, this is taken straight from the ruby docs:
\cx or \C-x control character, where x is an ASCII printable character
\M-x meta character, where x is an ASCII printable character
\M-\C-x meta control character, where x is an ASCII printable character
\M-\cx same as above
\c\M-x same as above
\c? or \C-? delete, ASCII 7Fh (DEL)
void
In Ruby, you can use ()
to represent nil
, as it’s treated like an empty expression with no statements. This leads to the ability to do all kinds of weird things, like !()
which evaluates to true
.
tl;dr
Ruby is so expressive that it provides you with many, many ways of doing things. This stands in stark contrast to many languages (for example in The Zen of Python where it states “There should be one– and preferably only one –obvious way to do it”).
Of course this approach has tradeoffs. While it can provide you with the ability to ship code incredibly quickly, it can also slow down quick skimming if there are myriad approaches to doing things. This is one of the explicit goals of prettier
and the Ruby plugin - we’re trying to standardize so that you can get back to writing application code.