This is a transcript of the lightning talk I gave at Brighton Ruby Conference 2023 last week.

It was filmed, and a video is available.

Hi, I’m Paul.

I’ve been working with Ruby since the early 2000s. Ruby’s changed a lot in that time, but we don’t always remember how much.

I’ve written a short demo program. I’m going to fix it to run in older versions of Ruby so we can see what’s changed.

Each of these year slides shows the biggest selling song of the year. This year, so far, it’s Flowers by Miley Cyrus.

It’s 2023. The current Ruby version is 3.2.2.

I’ve scraped a list of monster films off IMDB into a JSON file and written a program that processes it and lists every Godzilla film, sorted by year.

Here’s a sample of the JSON data file.

I’ve implemented a Collection class that lets me iterate over objects defined in a JSON file.

I’ve defined an immutable data object with a pretty to_s method using emoji stars to test Unicode handling.

This program loads up the collection, filters it by name and score, and prints the result.

The output looks like this, only longer.

Now I’ll try running it in older versions of Ruby.

There are three rules. No errors, no warnings, and the output should be identical, which I’ll test using diff.

It’s 2022, a year ago. The current Ruby version is 3.1.2.

Data.define doesn’t exist

I can use Struct with keyword_init instead. It’s not immutable, but it works the same.

And my program now runs in Ruby 3.1.

Another year back and it’s 2021, the year of the 2020 Summer Olympics.

We bought a house. It’s still a mess.

The current Ruby version is 3.0.1.

One line pattern matching is experimental. I’ll expand it. I’m not sure I like this, but it works.

The anonymous block argument isn’t supported. I could change it to something like &blk, or, as I’m not using it, just drop it entirely.

With that, it works in Ruby 3.0.

It’s three years ago. We all know what happened in 2020.

The current Ruby version is 2.7.1.

Pattern matching is experimental, so to avoid warnings I’ll get rid of it entirely, and use boolean expressions instead.

I can’t use a one line method definition. On the upside, my productivity in lines of code is trebled, helping me avoid being sacked by my weird new boss who got his money from an apartheid emerald mine.

That’s all I need for the program to work in Ruby 2.7.

Let’s go back 10 years, to 2013. A time when no one had heard of Covid – or Brexit.

But also, a time when no one had heard of #MeToo: the best selling song this year was Blurred Lines by Robin Thicke and Pharrell Williams. Or, legally, by Marvin Gaye. I hesitated to put this in, and I had to work to find a suitable screencap, but I think it does show how things have changed a bit for the better.

The current Ruby version is 2.1.0.

Ranges can’t be endless. I’ll use greater-than-or-equal instead.

The squiggly heredoc is gone, and it’s just that little bit harder to keep my code tidily indented.

I can’t use keyword arguments with Struct any more, so I’ll modify this to accept a hash and assign the attributes.

There’s no boolean match? method on strings or regular expressions. I can use the plain old match without a question mark, or the cryptic equals squiggle. I choose cryptic.

That’s it. The program works in Ruby 2.1.

Ruby 2.0 never really happened, so to go back to a previous significant release, we end up 12 years ago in 2011, when Ruby 1.9.3 was released.

The __dir__ kernel method is gone. This is how we used to do it. More code, less clear, but it works.

Ruby does know about UTF-8, but I have to tell it that the source file uses a non-ASCII encoding by adding a magic comment at the top.

def no longer returns a symbol, so I have to define my private methods the old way. And now it works in Ruby 1.9.

Let’s go back a decade and a half, to 2008. This is the era of the X Factor.

The Great Recession has just started. This would have been a great time to buy a house if I’d had any money.

Ruby 1.8.7 has just been released. This will be the last of the 1.8 line.

When chaining methods over multiple lines, the dot must be at the end of a line, not the start.

JSON isn’t included by default, but it is available as a gem. To use rubygems, I have to pass -rubygems to ruby.

This is actually the -r flag telling Ruby to require a file called ubygems.rb. A nice little hack.

Where have those dots gone? The core library doesn’t understand Unicode, so I’ll have to do the counting instead.

My program now works in Ruby 1.8.

Let’s go back 20 years all the way to 2003. The current Ruby version is 1.6.8, released last Christmas Eve, as is traditional.

Fully qualified names in class and module definitions aren’t supported. I have to reopen the module first before I can define the class.

There are no Rubygems. No one has written a JSON implemenation for Ruby; the RFC for JSON won’t even be written for another three years.

So I wrote my own. It wasn’t that bad: it took me half an hour or an hour or so. At least if you’re going to have to write a parser, JSON is fairly straightforward.

The core library is leaner, so some things are just a little bit more verbose.

I don’t have Symbol#to_proc. I have to write out the block the long way.

Once again, the core library is smaller, and I don’t have sort_by. I’ll use the spaceship operator instead.

A block after Struct.new does nothing. I might as well use a class with attr_accessor instead.

And with that, the program now works in Ruby 1.6.

What’s the performance of this program over time? Because as we all know, progress always proceeds in an upwards direction and the present is always better than the past, right?

I elided this earlier, but I also built in a benchmarking option that runs the filter 10,000 times. Take this with a pinch of salt, because this is a toy program that isn’t necessarily representative of real-world performance.

From 1.6.8 to 2.7.1, it gets faster with each version. We see gains from the introduction of YARV in 1.9.

But then it gets worse again! Perhaps all the new syntax features are making it slower.

Yes, to an extent. If I run the 2.7 code, which was the fastest, under 3.2, it works perfectly, but runs much faster.

What has changed in 20 years? Ruby’s syntax has changed a lot. The core and standard libraries are much richer. We have easy access to a huge amount of code through rubygems. Performance is better (mostly!).

But backwards compatibility is better than you might think. A lot of code written for Ruby 1.6 or 1.8 still works.

References

You can find the code used, along with references, in the accompanying git repository.