Chapter 1. Ruby 2.1
When the first edition of Ruby Cookbook was published in 2006, Ruby 1.8.4 was the state of the art and Rails had just reached 1.0. Eight years and more than 100 stable releases later, the latest version is now Ruby 2.1.1 and Rails has just reached 4.1.0. Over the last eight years, a lot has changed, both big and small:
-
A bytecode interpreter replaced the old Ruby MRI.
-
RubyGems and Rake became part of the standard library.
-
SOAP and Curses have moved out of the standard library into RubyGems.
-
New syntax primitives have been added for hashes, procs, and more.
-
New methods like
Object#tap
andString#prepend
have been added. -
New classes like
BasicObject
,Fiber
, andTracePoint
have been added. -
The
MD5
standard library was renamedDigest::MD5
. -
And much more…
The end result is a cleaner language that runs faster and more efficiently than ever before. For example, a simple Rails application is 167–200% faster in Ruby 2.1 than 1.8.
For all that has changed, there is thankfully very little that has been broken in terms of backward compatibility. The vast majority of code written for Ruby 1.8 will work in Ruby 2.1 without any modifications. However, and somewhat obviously, if you write code for Ruby 2.1, it will likely not work in Ruby 1.8 with some of the syntax changes introduced.
In between Ruby 1.8 and 2.1 were two other major releases: 1.9 and 2.0. In this chapter, we will group all the changes from versions 1.9 through 2.1 together instead of pointing out the specific dot release in which a feature was added or modified. For example, the YARV bytecode interpreter was added only in Ruby 1.9.4, but we will talk about it as just one of the many differences between Ruby 1.8 and 2.1.
1.1 What’s Different Between Ruby 1.8 and 2.1?
Problem
You want to know the major differences between Ruby 1.8 and 2.1.
Solution
Table 1-1 shows the major changes between Ruby 1.8 and 2.1.
1.2 YARV (Yet Another Ruby VM) Bytecode Interpreter
Solution
Since Ruby started in 1995, it originally used the MRI (Matz’s Ruby Interpreter) to interpret Ruby code. Written in C, the MRI (also known as CRuby) was the de facto reference implementation of the Ruby spec until Ruby 1.9.0 was released in 2007. With Ruby 1.9.0, the interpreter was changed from MRI to YARV (Yet Another Ruby VM).
One of the biggest differences between MRI and YARV was the introduction of a bytecode interpreter. With any programming language, the first step to running your code is to tokenize and parse its syntax. The MRI would mix parsing syntax with executing your code, which ended up being prone to memory leaks and slow execution times. The YARV interpreter separates parsing from the running of your code.
The bytecode interpreter takes the syntax tree and passes it to a virtual machine emulator that knows how to translate the bytecode into machine code. The emulator is tuned and optimized for the underlying hardware and knows how to translate instructions to PowerPC or x86 instructions. The result in more efficient execution, less memory usage, and a faster language.
Discussion
To understand bytecode interpreters better, let’s examine a simple Ruby syntax tree (also known as S-expressions):
require
'ripper'
Ripper
.
sexp
(
"1+1"
)
# => [:program, [[:binary, [:@int, "1", [1, 0]], :+, [:@int, "1", [1, 2]]]]]
If you have any familiarity with Lisp, you may notice some similarities between a syntax tree and any Lisp dialect. For example, let’s replace the brackets with parentheses and see if the code looks any more familiar:
(
program
(
binary
(
int
1
(
1
0
))
+
(
int
1
(
1
2
))
)
)
The reason that S-expressions look like Lisp is because essentially Lisp is a programming language built directly with S-expressions.
The YARV RubyVM takes these S-expressions and turns them into bytecode. To see what Ruby bytecode looks like, you can use the RubyVM
class:
require
'pp'
pp
RubyVM
:
:InstructionSequence
.
compile
(
'1+1'
)
.
to_a
# ["YARVInstructionSequence/SimpleDataFormat",
# 2,
# 0,
# 1,
# {:arg_size=>0, :local_size=>1, :stack_max=>2},
# "<compiled>",
# "<compiled>",
# nil,
# 1,
# :top,
# [],
# 0,
# [],
# [1,
# [:trace, 1],
# [:putobject_OP_INT2FIX_O_1_C_],
# [:putobject_OP_INT2FIX_O_1_C_],
# [:opt_plus, {:mid=>:+, :flag=>256, :orig_argc=>1, :blockptr=>nil}],
# [:leave]]]
Bytecode is not nearly as easy to read as S-expressions because the bytecode is the actual instructions sent to the VM, which turn into processor instructions.
The YARV bytecode interpreter is not the only interpreter available to Ruby developers. There is JRuby, Rubinius, MagLev, MacRuby, IronRuby, and Ruby Enterprise Edition (aka REE). Each one is built for a different purpose. For example, JRuby takes pure Ruby syntax and compiles it into Java bytecode instead of YARV bytecode. This allows you to run nearly any Ruby code on any machine running Java.
1.3 Syntax Changes
Solution
There were three major and two minor syntax additions to Ruby between 1.8 and 2.1.
The three major additions were defining hashes, defining methods, and defining procs.
The two minor additions were in arrays of symbols and defining rationals.
The most obvious syntax addition is for defining hashes. Here is the new way you can do it:
old_way
=
{
:foo
=>
"bar"
,
:one
=>
1
}
new_way
=
{
foo
:
"bar"
,
one
:
1
}
You can also apply the same hash syntax when calling methods that take hashes:
def
some_method
(
hash
=
{})
# do stuff
end
some_method
(
:foo
=>
"bar"
)
some_method
(
foo
:
"bar"
)
You can visually see how this can save you 25% of your keystrokes. Fewer keystrokes leads to fewer typos and bugs. Therefore, this new way of specifying hashes is being quickly adopted and you will see it throughout this book. The old way still works and is not deprecated, but the new way will save you a lot of time over your career with Ruby.
This new syntax for defining hashes has also inspired new keyword arguments for method definitions:
# OLD
def
old_way
(
options
=
{})
return
options
[
:foo
]
end
# => nil
old_way
(
:foo
=>
"bar"
)
# => "bar"
old_way
# => nil
# NEW UNNAMED KEYWORD ARGUMENTS
def
new_way
(
**
options
)
return
options
[
:foo
]
end
# => :new_way
new_way
(
foo
:
"bar"
)
# => "bar"
new_way
# => nil
# NEW NAMED KEYWORD ARGUMENTS
def
new_way
(
foo
:)
return
foo
end
# => :new_way
new_way
(
foo
:
"bar"
)
# => "bar"
new_way
# ArgumentError: missing keyword: foo
It is interesting to note that def
now returns the symbolic name of the method instead of nil
. This allows you to string together private
and public
calls when defining your classes:
class
Foo
private
def
baz
return
"yay"
end
def
bar
baz
end
end
Foo
.
new
.
baz
# NoMethodError: private method `bar' called for #<Foo:0x007f6b4abbbc98>
Foo
.
new
.
bar
# => "yay"
The last big syntax addition is a new way to define procs:
old_way
=
Proc
.
new
{
|
a
,
b
|
a
+
b
}
old_way
.
call
(
1
,
2
)
# => 3
new_way
=
->
(
a
,
b
)
{
a
+
b
}
new_way
.
call
(
1
,
2
)
# => 3
This is not only shorter to implement (fewer characters), but it is also consistent with the def
method of listing arguments (i.e., it uses parentheses instead of pipes).
The first smaller addition to Ruby syntax is specifying arrays of symbols:
old_way
=
[
:foo
,
:bar
,
:baz
]
new_way
=
%
i
(
foo
bar
baz
)
The second smaller addition to Ruby syntax is a shortcut for defining Rational numbers:
old_way
=
Rational
(
6
,
5
)
new_way
=
1
.
2
r
All of the syntax additions share the same goal: brevity in keystrokes.
See Also
1.4 Keyword Arguments
Solution
As of Ruby 2.0, you can define Ruby methods in new ways thanks to the idea of keyword arguments. Here is an example of the most complicated method definition you can possibly do now that has every permutation in it:
def
foo
(
a
,
b
=
"b_default"
,
*
c
,
d
:,
e
:
"e_default"
,
**
f
,
&
g
)
# do stuff
end
-
a
: Required positional argument -
b
: Optional positional argument with a default value -
c
: Splat positional arguments that lack default values -
d
: Declared keyword argument -
e
: Declared keyword argument with a default value -
f
: Double splat keyword arguments that lack default values -
g
: Block argument
Discussion
In Ruby 2.1, hashes were upgraded in many ways. For example, the old trick of using def foo(bar={})
to accept keyword arguments was made into a first-class citizen with the double-splat (**
) syntax.
Another way in which hashes were improved was that they preserved their internal order. In Ruby 1.8, the order in which you inserted items into a hash would have no correlation to the order in which they were stored, and when you iterated over a hash, the results could appear totally random. Now hashes preserve the order of insertion, which is clearly useful when you are using them for keyword arguments in method definitions.
The new keyword arguments are a great way to save time while coding. Even a few keystrokes per method can add up quickly.
1.5 Performance Enhancements
Solution
There are few places that haven’t been internally improved over the last eight years: however, we will touch on a few major areas of enhancements.
The biggest performance enhancements came from the new YARV interpreter, which was discussed in Recipe 1.2.
One of the other large performance-enhancing features of Ruby has been the addition of the lazy
method to many basic classes, like Array
and Hash
, through the Enumerator
class:
array
=
[
1
,
2
,
3
].
lazy
.
map
{
|
x
|
x
*
10
}
.
select
{
|
x
|
x
>
10
}
# => #<Enumerator::Lazy>
# No calculations are performed until a method is called to the array object
array
.
to_a
# => [20, 30]
For small arrays like this, the benefit is not clear. However, as you deal with large data and start chaining multiple enumeration methods together, the use of lazy evaluation prevents you from using unnecessary amounts of memory in temporary variables. Here is an example:
def
search_file
(
file_name
,
term
)
File
.
open
(
file_name
)
do
|
file
|
file
.
each
.
flat_map
(
&
:split
)
.
grep
(
term
)
end
end
The flat_map
implementation internally uses lazy enumeration automatically. This means that you are going to iterate over the array only once, instead of twice as you might expect since you run two chained enumeration methods.
Another area where lazy evaluation has had a dramatic effect is in increasing performance with the Ruby garbage collector, since fewer objects are created to clean up in the first place. A lot more has also changed in GC
between Ruby 1.8 and 2.1, including a new algorithm for garbage collection called Bitmap Marking. The new algorithm implements a “lazy sweep,” which dramatically reduces overall memory consumption by all Ruby processes.
Another area of improvement is in the require
method and File
and Pathname
classes. They were refactored, which helps considerably for the initial loading times to start complicated frameworks like Rails. One example of the refactoring was that Ruby 1.8 rechecked the $LOAD_PATH
to make sure it is all expanded on every require. This change led to a 35% reduction in initial loading time for a simple Rails app.
Stack tracing performance has improved up to 100× between Ruby 1.8 and 2.1 by allowing you to limit the number of frames requested.
The test/unit
library was updated to be able to run in parallel, which speeds up unit testing.
There have been many more areas of performance improvements, but these contribute most to the nearly 2× better performance of Ruby 2.1 over Ruby 1.8.
See Also
-
Read more about YARV in Recipe 1.2
-
Read more about the new GC algorithm at http://bit.ly/ruby_2_0_gc
-
Watch a presentation about the Ruby 2.1 GC at http://bit.ly/ruby_2_1_gc
1.6 Refinements
Solution
As of Ruby 2.0, you can use the refine
and using
methods to monkey-patch safely within a given context. Here is an example:
module
MyMonkeyPatches
refine
String
do
def
length
30
end
end
end
class
TestMyMonkey
using
MyMonkeyPatches
def
string_length
(
string
)
string
.
length
end
end
string
=
"foobar"
string
.
length
# => 6
TestMyMonkey
.
new
.
string_length
(
string
)
# => 30
string
.
length
# => 6
Notice that the entire scope of your monkey-patching stays within your class.
Discussion
Refinements were an experimental feature until Ruby 2.1, but are now mainstream. The ability to dynamically add and modify functionality of classes at any time is both powerful and dangerous. If you don’t like the way something works in Ruby, you can always monkey-patch it. However, the dangerous part is the side effects that you do not anticipate.
In the example within this recipe, you can clearly see that changing the way String#length
works to be static can be a bad idea. However, when it is scoped to a special module to encapsulate the refinement, the potential damage is strictly limited.
1.7 Debugging with DTrace and TracePoint
Problem
You want to debug your Ruby app in real time.
Solution
Ruby 2.1 gives you two new and powerful ways to debug your Ruby application: DTrace and TracePoint.
With DTrace, you use the D language for making queries about a running process. Here is the basic syntax for the D language:
probe
/test/
{
action
}
A probe runs the test and if it passes, runs the action. A probe looks like this:
provider
:
module
:
function
:
name
Modules and functions are optional. There are a number of different probe names available within Ruby, but for this example, we will just use the method-entry
probe:
$
sudo dtrace -q -n'ruby*:::method-entry \
{ printf("%s\n", copyinstr(arg0)) }'
-c"rake environment"
rake aborted! No Rakefile found(
lookingfor
: rakefile, Rakefile, rakefile.rb, Rakefile.rb)
(
See full trace by running task with --trace)
RbConfig RbConfig RbConfig RbConfig RbConfig RbConfig ...$
sudo dtrace -q -n'ruby*:::method-entry \
{ @[copyinstr(arg0), copyinstr(arg1)] = count(); }'
-c"rake environment"
rake aborted! No Rakefile found(
lookingfor
: rakefile, Rakefile, rakefile.rb, Rakefile.rb)
(
See full trace by running task with --trace)
FileUtils commands 1 Gem clear_paths 1 Gem default_path 1 Gem detect_gemdeps 1 Gem find_home 1 Gem marshal_version 1 ...
DTrace is very powerful, but you need to learn the D language to use it effectively. Alternatively, you can use TracePoint, which is built in to Ruby 2.1 as part of the core library. Here is an example of how to use TracePoint:
trace
=
TracePoint
.
new
(
:raise
)
do
|
t
|
puts
t
.
inspect
end
trace
.
enable
require
'doesnt_exit'
# => #<TracePoint:raise@[...]/kernel_require.rb:55>
# => #<TracePoint:raise@[...]/kernel_require.rb:141>
# => #<TracePoint:raise@[...]/workspace.rb:86>
# => LoadError: cannot load such file -- doesnt_exit
Discussion
DTrace is a dynamic tracing framework created by Sun originally to debug both kernel and app code in real time. It is a very sophisticated and flexible tool, but the learning curve is steep because you have to become familiar with a new system.
TracePoint is part of core Ruby and available in every Ruby 2.1 environment. Its wide availability combined with the fact that it is written in Ruby make it an easy way for any Ruby developer to debug his or her application.
If you want to debug your application such that any raised error will dump you into an interactive Ruby environment automatically, you can combine TracePoint with the debug
library by adding this simple code to your app:
# fun_with_debug.rb
trace
=
TracePoint
.
new
(
:raise
)
do
|
t
|
require
'debug'
end
trace
.
enable
require
'doesnt_exit'
And then you can see the code in action by just running it:
$
ruby fun_with_debug.rb Debug.rb Emacs support available.[
...]
/kernel_require.rb:57: RUBYGEMS_ACTIVATION_MONITOR.enter(
rdb:1)
1.8 Module Prepending
Problem
You want to allow modifications to class methods while retaining setup and teardown logic for those methods. For example:
module
MyHelper
def
save
puts
"before"
super
puts
"after"
end
end
class
MyBadClass
include
MyHelper
def
save
puts
"my code"
end
end
MyBadClass
.
new
.
save
# => my code
Notice that you were hoping that the before and after text showed up.
Solution
Ruby 2.1 has a new alternative to include
called prepend
:
module
MyHelper
def
save
puts
"before"
super
puts
"after"
end
end
class
MyGoodClass
prepend
MyHelper
def
save
puts
"my code"
end
end
MyGoodClass
.
new
.
save
# => before
# => my code
# => after
Discussion
The way that prepend
works is pretty simple when you inspect the class hierarchy:
def
parents
(
obj
)
(
(
obj
.
superclass
?
parents
(
obj
.
superclass
)
:
[]
)
<<
obj
)
.
reverse
end
parents
(
MyGoodClass
)
# => [Class, Object, BasicObject, Module]
parents
(
MyBadClass
)
# => [MyBadClass, BasicObject, Object]
prepend
puts the MyHelper
module at the top of the class hierarchy, before the definitions in the class itself. include
puts the MyHelper
at the very bottom of the class hierarchy so it is overwritten when the class is defined.
1.9 New Methods
Solution
With over 70 new methods since Ruby 1.8, it can be hard to figure out which ones merit particular attention. This chapter has already covered some good ones like Enumerable#lazy
, Module#refine
, and Module#using
. However, there are a few more examples of some useful methods you may not have used yet.
People who love O(log n) Array searching will really enjoy Range#bsearch
:
ary
=
[
0
,
4
,
7
,
10
,
12
]
(
0
.
.
.
ary
.
size
)
.
bsearch
{
|
i
|
ary
[
i
]
>=
4
}
#=> 1
(
0
.
.
.
ary
.
size
)
.
bsearch
{
|
i
|
ary
[
i
]
>=
6
}
#=> 2
(
0
.
.
.
ary
.
size
)
.
bsearch
{
|
i
|
ary
[
i
]
>=
8
}
#=> 3
(
0
.
.
.
ary
.
size
)
.
bsearch
{
|
i
|
ary
[
i
]
>=
100
}
#=> nil
The Exception#cause
method keeps track of the root cause of your errors. This is very handy when your rescue code has a bug in it. In Ruby 1.8, the following code would have raised a “method doesn’t exist” error:
begin
require
'does_not_exist'
rescue
nil
.
some_method
end
# LoadError: cannot load such file -- does_not_exist
Gaining insight into the garbage collector is one of the nice capabilities Ruby 2.1 provides:
require
'pp'
pp
GC
.
stat
# {:count=>5,
# :heap_used=>138,
# :heap_length=>138,
# :heap_increment=>0,
# :heap_live_num=>28500,
# :heap_free_num=>42165,
# :heap_final_num=>0,
# :total_allocated_object=>105777,
# :total_freed_object=>77277}
One little helper method that is handy is Kernel#dir
instead of just Kernel#FILE
:
puts
__dir__
# /home/user/ruby_app/
Another little helper that is useful is Kernel#require_relative
, which allows you to require a local Ruby file:
# old way
require
File
.
expand_path
(
File
.
join
(
File
.
dirname
(
__FILE__
),
".."
,
"lib"
,
"mylib"
)
)
# new way with __dir__
require
File
.
expand_path
(
File
.
join
(
__dir__
,
".."
,
"lib"
,
"mylib"
)
)
# new way with require_relative
require_relative
File
.
join
(
".."
,
"lib"
,
"mylib"
)
For sysadmins who need network information, Socket.getifaddrs
is your new best friend:
require
'socket'
require
'pp'
pp
Socket
.
getifaddrs
# => [#<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000
# PACKET[protocol=0 lo hatype=772 HOST hwaddr=00:00:00:00:00:00]>,
# #<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000
# 127.0.0.1 netmask=255.0.0.0>,
# ...
An interesting new method is Enumerable#chunk
, which will create subarrays based on repeated information. The next example shows how to use Enumerable#chunk
to separate the vowels from the consonants in a sentence. The chunk
method is lazy, so no interstitial objects are created in the process of iteration:
"the quick brown fox"
.
each_char
.
chunk
do
|
letter
|
%w{a e i o u}
.
include?
(
letter
)
?
"vowel"
:
"consonant"
end
.
each
do
|
type
,
letters
|
puts
"
#{
type
}
:
#{
letters
.
join
}
"
end
# consonant: th
# vowel: e
# consonant: q
# vowel: ui
# consonant: ck br
# vowel: o
# consonant: wn f
# vowel: o
# consonant: x
And finally, a simple string method, String#prepend
, might just make your life a little life easier:
"world"
.
prepend
(
"hello "
)
# => "hello world"
1.10 New Classes
Solution
With over nine new classes since Ruby 1.8, it can be hard to figure out which ones merit particular attention. This chapter has already covered some good ones like TracePoint
, RubyVM
, and Enumerator::Lazy
. However, there are a few more examples of some useful classes you may not have used yet.
The Fiber
class is an interesting alternative to threads. The biggest difference is that fibers are never preempted and scheduling must be done by the programmer, not the VM. Here is what we mean:
thread
=
Thread
.
new
do
puts
"Hello world!"
end
# Hello world!
fiber
=
Fiber
.
new
do
puts
"Hello world!"
end
fiber
.
resume
# Hello World!
So you can see that Fiber
is more in your control than threads, because threads run instantly. However, you can do more with Fiber
too:
fiber
=
Fiber
.
new
do
|
multiply
|
Fiber
.
yield
multiply
*
10
Fiber
.
yield
multiply
*
10_000_000
"done"
end
fiber
.
resume
(
2
)
# => 20
fiber
.
resume
(
2
)
# => 20000000
fiber
.
resume
(
2
)
# => "done"
fiber
.
resume
(
2
)
# FiberError: dead fiber called
The Encoding
class shows how much Ruby has progressed in terms of character encodings since 1.8. The old hacks are gone, and UTF-8 is now standard with great and simple ways to convert strings natively built into the language:
require
'pp'
pp
Encoding
.
list
# [#<Encoding:ASCII-8BIT>,
# #<Encoding:UTF-8>,
# #<Encoding:US-ASCII>,
# #<Encoding:UTF-16BE (autoload)>,
# #<Encoding:UTF-16LE (autoload)>,
# #<Encoding:UTF-32BE (autoload)>,
# #<Encoding:UTF-32LE (autoload)>,
.
.
.
string
=
"some string \u2764"
# <-- this will output a heart
string
.
encoding
# => #<Encoding:UTF-8>
string
=
string
.
encode
(
Encoding
:
:ISO_8859_1
)
# Encoding::UndefinedConversionError: U+2764 from UTF-8 to ISO-8859-1
string
=
string
.
force_encoding
(
Encoding
:
:ISO_8859_1
)
# => "some string \xE2\x9D\xA4"
string
.
encoding
#=> #<Encoding:ISO-8859-1>
The Random
class gives you more control over generating random numbers than the simple Kernel#rand
method. In fact, the Random.rand
method provides the base functionality of Kernel#rand
along with better handling of floating-point values:
Random
.
rand
# => 0.8929923189358412
seed
=
1234
random_generator
=
Random
.
new
(
seed
)
random_generator
.
rand
# => 0.1915194503788923
random_generator
.
rand
# => 0.6221087710398319
random_generator2
=
Random
.
new
(
seed
)
random_generator2
.
rand
# => 0.1915194503788923
random_generator2
.
rand
# => 0.6221087710398319
random_generator2
.
seed
# => 1234
You can see that the Random
class allows you to create various generators with arbitrary seeds. In real life, you will want to pick a seed that is as random as possible. You can use Random.new_seed
to generate one, but Random.new
without any arguments will use Random.new_seed
automatically.
1.11 New Standard Libraries
Solution
With over 16 new standard libraries since Ruby 1.8, it can be hard to figure out which ones merit particular attention. This chapter has already covered some good ones like debug
and ripper
. However, there are a few more examples of some useful classes you may not have used yet.
The objspace
library is an object allocation tracing profiling tool that can be very useful for tracking down memory leaks:
require
'objspace'
require
'pp'
objects
=
Hash
.
new
(
0
)
ObjectSpace
.
each_object
{
|
obj
|
objects
[
obj
.
class
]
+=
1
}
pp
objects
.
sort_by
{
|
k
,
v
|
-
v
}
# [[String, 24389],
# [Array, 5097],
# [RubyVM::InstructionSequence, 1027],
# [Class, 449],
# [Gem::Version, 327],
# [Gem::Requirement, 292],
# [MatchData, 203],
# ...
The prime
library has the set of all prime numbers and is lazily enumeratable:
require
'prime'
Prime
.
each
(
100
)
do
|
prime
|
p
prime
end
# => 2, 3, 5, 7, 11, ...., 97
Prime
.
prime?
(
1
)
# => false
Prime
.
prime?
(
2
)
# => true
Here is a quick example of cmath
for trigonometric and transcendental functions for complex numbers:
require
'cmath'
CMath
.
sqrt
(
-
9
)
# => 0+3.0i
The shellwords
library manipulates strings according to the word parsing rules of bash. This is especially helpful for escaping user content for system commands:
require
'shellwords'
argv
=
Shellwords
.
split
(
'ls -la'
)
# => ["ls", "-la"]
argv
<<
Shellwords
.
escape
(
"special's.txt"
)
# => ["ls", "-la", "special\\'s.txt"]
command_to_exec
=
argv
.
join
(
" "
)
system
(
command_to_exec
)
1.12 What’s Next?
Problem
You want to know what is in store for Ruby 2.2 through Ruby 3.0 and beyond.
Solution
The changes from Ruby 1.8 through Ruby 2.1 have had an intense focus on backward compatibility. Very little has changed to make Ruby 1.8 code not compatible. A few rarely used libraries were removed and a few functions were renamed, but on the whole the focus was compatibility.
One of the big trends that we will continue to see as Ruby evolves is more and more standard libraries moving into gems. The decision to incorporate RubyGems into Core was made to slim down the standard libraries. Between Ruby 1.8 and Ruby 2.1, we saw 17 of 107 (16%) of the standard libraries either moved into RubyGems or removed completely. In the same amount of time, 17 new standard libraries were added, so it ended up as a wash. However, as Ruby development progresses, we will continue to see more library movement into RubyGems.
Another big trend that you can expect to continue to see is new syntax that reduces the number of keystrokes you have to type. The philosophy is that the more you have to type, the more opportunity you have to introduce bugs into your code. All five of the new syntax types added so far accomplished the goal of fewer keystrokes, and we might see more shortening syntax in the future.
There has been a lot of work done on the Ruby garbage collector alogorithms, including two overhauls. We will likely see more work to improve the garbage collection system in the future. We will also see more work on the YARV bytcode interpreter. One speculation is that you may in the future be able to compile your Ruby code into Ruby bytecode files that can be distributed as freely as Ruby source code (like Java bytecode files).
Matz has made it clear that throughout Ruby 2, backward compatibility is key. This has meant that anything that breaks backward compatibility is being explored with in Ruby 3. The roadmap and timeline for Ruby 3 is not clear yet, but you are not likely to see any dramatic changes to Ruby until that time.
See Also
Get Ruby Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.