With the Ruby 1.9.2 release on the horizon, it's time to get developers excited about Ruby 1.9. What are some nice things you can do in Ruby 1.9 that you can't do in Ruby 1.8?
I personally love the new hash syntax: {:a => 1}
becomes {a:1}
Enumerators.
["a", "b", "c"].map {|elem, i| "#{elem} - #{i}" }
# => ["a - ", "b - ", "c - "]
["a", "b", "c"].each_with_index.map {|elem, i| "#{elem} - #{i}" }
# => ["a - 1", "b - 2", "c - 3"]
Enumerable
methods returns an instance of Enumerator
when no block is passed to it. In this case, it's used to give map
an index
argument, taken from each_with_index
.
This has been backported to 1.8.7 as well.
I like Enumerators a lot -- not just for existing types, but writing my own collections as Enumerator classes. Since switching to 1.9, I've twice had to build API adapters to external Web services that pull down large JSON or XML result sets. Sometimes I'd be capped on how many records I could retrieve at once, meaning I'd need to do multiple requests. (Get the first 500, then get records 501 to 1000, etc.)
The "old" way I'd have processed these would have been to grab the first batch, iterate over it all at once with .each
or .collect
, and create an equal-sized array of Ruby objects. If I couldn't get all the records in one request, I'd loop through API requests as well, adding to the array each time. This means all the time is front-loaded, perceived as a slow retrieval, and I'm chewing up a lot of memory: for the source data, for an equal number of Ruby objects, and sometimes for intermediate array operations. This is wasteful when I'm probably only operating on one object at a time.
With Enumerators, I can grab the first batch, hold the source data as my "authoritative" collection, and process and yield each Ruby object as I step into it. When I pass the last element, if I know there's more data to be pulled from the source, I can make the next API call then. (I.e., lazy loading.) This means a much faster return on the retrieval method call, and much better memory usage. Each Ruby object is eligible for garbage collection as soon as I'm done with it and have moved to the next one.
An abstract implementation of the idea looks like this:
class ThingyCollection < Enumerator
attr_reader :total
# Returns a new collection of thingies.
def initialize(options={})
# Make the request for the first batch
response = ThingyAPIClient.get_thingies(options)
@total = response.total # Number of ALL thingies, not just first batch
records = response.data # Some array of JSON/XML/etc. from the API
# Create a closure which serves as our enumerator code
enum = Proc.new do |yielder|
counter = 0 # Initialize our iterator
while counter < @total
# If we're at the end of this batch, get more records
if counter == records.length
more = ThingyAPIClient.get_next_thingies(counter, options)
records += more.data
end
# Return a Ruby object for the current record
yielder.yield Thingy.new(records[counter])
counter += 1
end
end
# Pass that closure to the Enumerator class
super(&enum)
end
end
Once you have that, you can walk over them like:
thingies = ThingyCollection.new(foo: bar) # Whatever search options are relevant
puts "Our first thingy is #{thingies.next}"
puts "Our second thingy is #{thingies.next}"
thingies.rewind
thingies.each do |thingy|
do_stuff(thingy)
end
What do you lose? Mostly the ability to easily jump to a particular element by reference. (Which means you also lose "last," sorts, etc.) Just getting .next
and a couple of .each
variants is not as rich as array functionality, but for my most common use cases it's all i need.
Yes, you can do this with Ruby 1.8.7 thanks to backporting. But 1.9 is much faster at it thanks to the internal use of fibers. And if it hadn't been for 1.9, there wouldn't have been a 1.8.7, so I've decided it still qualifies as my favorite 1.9 feature.
Oh, and: it's more than twice as fast. Faster still if you're building a multithreaded application with lots of I/O delays. With all the work that still goes into trying to pump a bit more performance out of 1.8, or fix its threading, etc., it amazes me that people aren't getting more excited about 1.9's speed or its native threads.
instance_exec, and class_exec are great new features, but to me it's mainly the small changes (that have already been backported to 1.8.7). Things like Method#owner is great - ever wondered where exactly in the inheritance chain a particular method was defined? my_object.method(:blah).owner will tell you :)
Other things i like about 1.9 are the more consistent scoping rules esp. in eval contexts. It was a silly omission (IMO) that constants and class variables were not looked up in an instance_eval, 1.9 fixes this :)
I like Symbol#to_proc
, which saves you having to write a lambda every time you use a higher-order function. So whereas summing an array used to be arr.inject(0) {|memo, val| memo + val}
, now you can just write arr.inject(&:+)
, and rather than houses.collect {|house| house.price}
, you can write houses.collect(&:price)
.
Some libraries (e.g. ActiveSupport) provide the same functionality under 1.8, but it's still nice to have it be part of the core language, and the 1.9 implementation is a lot better optimized than the library approach.
Ruby 1.9 has different block behaviors:
Block parameters are always local to their block, and invocations of the block never assign values to existing variables:
Block syntax has been extended to allow you to declare block-local variables that are guaranteed to be local, even if a variable by the same name already exists in the enclosing scope.
Threads are also different:
Ruby 1.8 uses only a single native thread and runs all Ruby threads within that one native thread. This means threads are very lightweight but they never run in parallel.
Ruby 1.9 is different, it allocates a native thread for each Ruby thread. But because some C libraries used are not themselves thread-safe, Ruby is very conservative and never allows more than one of its native threads to run at the same time (this restriction may be relaxed in later releases)
Other minor change is the inclusion of RubyGems in the load path, no need to require "rubygems"
anymore.
Full native support for multibyte character encodings, particularly Unicode.
I can't believe that this hasn't been mentioned yet: the single biggest feature of Ruby 1.9.2+ is that for the first time in 17 years, Ruby will have a specification.
You might have heard that all release schedule for Ruby 1.9.2 (which was supposed to be released in the Spring of 2010) has been canceled, and this is the reason: first, a complete specification of Ruby 1.9.2 will be developed in the RubySpec project, then Ruby 1.9.2 (the programming language) will be released, and only then will YARV 1.9.2 be released, after it passes the RubySpec testsuite.
This is exactly backwards from how it worked before: first MRI was released, then all the other implementors read the (not very well designed, and generally badly documented) C source code of MRI, to try and figure out what the heck that new feature was supposed to do, then they tried to write executable specifications, and only then did they even have a remote chance of actual compatibility. But by that time, generally, a new version of YARV had already been released, and the cycle began anew ... Not to mention that the maintainers of MRI and YARV didn't even run the RubySpecs.
This has huge ramifications. For example, despite the fact that currently more than a dozen different Ruby implementations are in active development, and over the years of its existence there have been more than 30 different implementations of the Ruby programming language, this fact has not been acknowledged by the maintainers of the Ruby programming language. For them, Ruby and MRI (or more recently Ruby and YARV) have always been one and the same thing: MRI was both the language and the execution engine, Ruby was both the execution engine and the language. The "specification" of the Ruby programming language was the C source code of MRI.
As of five weeks ago, this has changed: now, the official specification of the Ruby programming language (at least version 1.9.2 and later) is the executable testsuite of the RubySpec project. And YARV is just another Ruby implementation, completely equal to MacRuby, IronRuby, JRuby, Cardinal, tinyrb, SmallRuby, BlueRuby, MagLev and the others.
This means that so called "alternate" implementations (which as of now should no longer be called "alternate" because YARV has lost its special status) now have a chance to actually catch up with the latest language features implemented in YARV. In fact, since most of the other implementations are actually both much better designed and implemented in much better languages than YARV (which is basically a huge spaghetti mess of C), plus having more manpower, it is entirely plausible that other implementations will actually be Ruby 1.9.2 compliant before YARV.
Ruby 1.9.2 supports gettting information about method parameters. You can get the name of the parameters, and information about them such as optional, required or block.
View Method#params for an example.
Ruby 1.9 Fibers offers a powerful new concurrency construct. Ruby Fibers: 8 Useful Reads On Ruby’s New Concurrency Feature has links to fibers related articles.
YARV. The new Ruby VM in 1.9 offers a new modern VM that is significantly faster.
Hashes are ordered in ruby 1.9. It's very useful when implementing some algorithms. You have to depend on a gem or write your own ordered hash in ruby 1.8.