New Methods in Ruby 2.2
Most of the fanfare around Ruby 2.2 has focused on garbage collection (GC) upgrades. The GC will now clean up symbols and has a new incremental algorithm that reduces the pause time. These changes are very exciting, but there were some new methods added as well. Let’s see what other new toys 2.2 delivered.
Binding#local_variables
If you’ve ever wanted to know the local variables defined in a scope, have I got a method for you. Stick this anywhere and find out what vars are in use:
def addition(x, y)
puts binding.local_variables.inspect
z = x + y
puts binding.local_variables.inspect
z
end
> addition(2, 3)
[:x, :y, :z]
[:x, :y, :z]
5
Binding#receiver
Maybe you don’t care about the local vars. Do you want to know which object is receiving the method call?
class Cat
def self.type
binding.receiver
end
end
> Cat.type
Cat
class Tiger < Cat
end
> Tiger.type
Tiger
Most of us don’t directly deal with bindings, but if you do life just got a little easier.
Dir#fileno
Getting the file descriptor for an IO
object is nothing new. Now you can get it for a directory too. File descriptors are a way to reference a particular open file, directory, etc. You’ve probably used them before without even realizing it. On POSIX systems, 0, 1, and 2 are reserved for standard in, standard out, and standard error, respectively.
Have you ever used something like 2>&1
on the command line? You were referencing the file descriptors.
> $stdout.fileno
1
> Dir.new('.').fileno
8
Be warned, this new method is only available on POSIX systems. If you try to call it on Windows, it’ll throw a NotImplementedError
.
Enumerable#slice_after
This method is a complement to the existing slice_before
method. I haven’t run across slice_before
in the wild so I had to take a look at how it works.
As the name suggests, slice_before
is used to slice and dice enumerables. Given a way to match an element in the enumerable, it will find a match and cut it apart just prior to the match:
> [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_before { |e| e.is_a?(Integer) }.to_a
[[1, "a"], [2, "b", "c"], [3, "d", "e", "f"]]
In this case, it made cuts before the integers 1
, 2
, and 3
.
An argument can be passed in place of a block. When that happens, the argument is checked using ===
(i.e. case equality). We could get the same result as above by passing Integer
:
> [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_before(Integer).to_a
[[1, "a"], [2, "b", "c"], [3, "d", "e", "f"]]
Want to guess at what slice_after
does? Rather than slicing before the match, it slices after:
> [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_after(Integer).to_a
[[1], ["a", 2], ["b", "c", 3], ["d", "e", "f"]]
Enumerable#slice_when
A particularly fun addition is slice_when
. Unlike slice_after
, this method only accepts a block. It walks an enumerable, passing pairs of elements to the block. When the block returns true
, the enumerable is sliced between the pair of elements:
> [1, 3, 4, 5, 7, 8, 9, 10, 12].slice_when { |a, b| a + 1 != b }.to_a
[[1], [3, 4, 5], [7, 8, 9, 10], [12]]
Here, we’ve found runs of consecutive numbers by slicing when the second number is not the first number plus one.
Given an array of numbers, you could easily find out how many of each one you have:
> Array.new(10) { rand(3) + 1 }.sort.slice_when(&:!=).map { |x| [x.first, x.size] }
[[1, 4], [2, 4], [3, 2]]
On this run, we got 1
four times, 2
four times, and 3
twice.
Float#next_float, Float#prev_float
These functions return the next or previous representable float. Note the word “representable” in that sentence, not all floats can be represented.
> 1.0.next_float
1.0000000000000002
Notice how it skipped 1.0000000000000001
? It can’t be represented using a Float
:
> 1.0000000000000001
1.0
It’s also worth noting that the distance between two steps isn’t always the same:
> 2.0.prev_float
1.9999999999999998
> 2.0.next_float
2.0000000000000004
Apparently, methods like this are useful for finding ULP values and doing other things that you’ll most likely never do. They might not be the most globally useful methods, but if you need them you’ll be glad they’re there.
File.birthtime, File#birthtime, File::Stat#birthtime
We’ve had atime
, ctime
, and mtime
to check a variety of access and modification times related to a file. What we haven’t had before 2.2 is the birth time (i.e. creation time) for a file. You’ll notice that it, like the others, comes in both class and instance flavors:
> File.new('test', 'w').birthtime
2015-01-06 19:24:44 -0600
> File.birthtime('test')
2015-01-06 19:24:44 -0600
Also available in File::Stat
:
> File::Stat.new('test').birthtime
2015-01-06 19:24:44 -0600
Kernel#itself
Ruby went out and got itself an identity method. For those not familiar, an identity method returns the object it’s called on:
> 1.itself
1
At this point you might be wondering where this is useful. One of the most common examples is grouping:
> [2, 3, 3, 1, 2, 3, 3, 1, 1, 2].group_by(&:itself)
{2=>[2, 2, 2], 3=>[3, 3, 3, 3], 1=>[1, 1, 1]}
It can also be used as a way to no-op a method like map
or select
. In functional programming, this can be a good way to avoid what might otherwise be an awkward conditional.
Method#curry
You might not have realized that Ruby is capable of currying and partial application. In the past, you could only call curry
on a Proc
. This same power is now available to you on Method
.
def sum(*args)
args.reduce(:+)
end
> inc = method(:sum).curry(2).(1)
#<Proc:0x007ff68ac96728 (lambda)>
> inc.(3)
4
Method#super_method
Earlier we found the object receiving a method with binding.receiver
. We can also find out about the method’s parent. Calling super_method
returns the method that you would get if you called super
. If the method has no parent, it returns nil
.
class Cat
def speak
'meow'
end
end
class Tiger < Cat
def speak
'roar'
end
end
> Tiger.new.method('speak')
#<Method: Tiger#speak>
> Tiger.new.method('speak').super_method
#<Method: Cat#speak>
> Cat.new.method('speak').super_method
nil
String#unicode_normalize, String#unicode_normalize!, String#unicode_normalized?
Did you know that, in Unicode, some characters can be represented multiple ways? Take for example, an “e” with an acute accent. You can represent it with normalization form canonical composition (NFC) using a single code point:
> nfc = "\u{e9}"
"é"
You can also represent it with normalization form canonical decomposition (NFD) by combining two code points:
> nfd = "\u{65}\u{301}"
"é"
In this form, a regular “e” is combined with “◌́” to create the “é” you see above. While these two might produce a similar looking output they are not the same:
> nfc == nfd
false
What unicode_normalize
does is convert strings from one form to another. By default it converts to NFC:
> nfc == nfd.unicode_normalize
true
You can also pass a symbol indicating the form to convert to:
> nfc.unicode_normalize(:nfd) == nfd
true
These forms exist because they have value in different situations. The NFC codes are shorter, but if you want to strip accents from a word for, let’s say search purposes, the NFD form would be easier to handle. While NFC and NFD are the most common, you can also pass :nfkc
and :nfkd
if needed.
The bang version of this does what you’d expect and modifies the string receiving the message.
Calling unicode_normalized?
checks to see if the string matches the form you want. It takes the same argument as unicode_normalize
:
> nfc.unicode_normalized?
true
> nfc.unicode_normalized?(:nfd)
false
Time to Refine
A lot of these methods aren’t every day tools. They’re incremental improvements that shore up the language. Ruby has experienced a lot of change in the last few years. The changes between 1.8, 1.9, and the 2.0 releases were monumental. Now, the language is focused on refining some of the rough edges created by that process. In fact, 2.2 lets you create quoted symbol keys in hashes with a trailing colon:
{ 'programming-language': :ruby }
No more symbol keyed hashes with an stray hash rocket thrown in because of a hyphened HTML attribute. I think that is something we can all get behind.