Posts
RuboCop 1.62 Introduces (Experimental) Support for Prism
Recently I wrote that it was already possible to run RuboCop with Ruby’s new Prism parser, but that required a bit of manual work. I also outlined some plans to add built-in Prism support in RuboCop. 1 Today I’m happy to report that last week we’ve released RuboCop 1.62 which features (experimental) support for Prism!
Now using RuboCop with Prism is just a matter of adding Prism to your
Gemfile
(assuming you’re using Bundler, that is):gem 'prism'
and adding the following to your RuboCop configuration:
AllCops: ParserEngine: parser_prism TargetRubyVersion: 3.3
Magic! Initial benchmarks we’ve done suggest that using Prism results in about 70% speedup of the parsing speed, but I’d take those with a grain of salt as we’re very early in the process of optimizing RuboCop and
rubocop-ast
for Prism, plus the results you’d get would be strongly correlated with what you’re parsing.2 At any rate - it’s pretty certain that Prism is faster thanwhitequark/parser
.Why is the Prism support considered “experimental”? There a few reasons for this:
- Prism currently supports parsing only Ruby 3.3+, compared to
whitequark/parser
supporting Ruby 2.0+. This means Prism is still not a complete replacement forwhitequark/parser
in all use-cases. - There are a bunch of small issues with Prism’s
parser
translation that have to be fixed. In other words - for the time being this means that some cops won’t be working properly with Prism. You can monitor the list of outstanding Prism issues related to RuboCop here. - We might decide to make some changes to the configuration options. (although this seems unlikely)
I expect that as more people start to use RuboCop with Prism we’ll quickly identify and fix any outstanding problems. I’d encourage the more adventurous people to play a bit with RuboCop and Prism and report their findings. (e.g. issues discovered, benchmarks, etc)
RuboCop will keep supporting
whitequark/parser
for the foreseeable future, so there’s no rush for anyone to switch to using Prism today. Also, it’s not like we can stop supportingwhitequark/parser
until we switch to using Prism’s AST format natively - currently we’re using Prism as a backend forwhitequark/parser
.3That’s all I have for you today. Keep hacking!
- Prism currently supports parsing only Ruby 3.3+, compared to
Weird Ruby: The Double Aliased Enumerable Method
Ruby is famous (infamous?) for giving us many ways to do the same thing. One aspect of this is that quite a few methods in core classes have aliases, especially in the
Enumerable
module. E.g.:collect
->map
inject
->reduce
detect
->find
select
->find_all
Most Rubyists are probably aware that the “original” method names (
collect
,inject
, etc) were inspired by Smalltalk and later the aliases where added to reflect the naming trends in the functional programming community.Enumerable#select
is kind of special, as since Ruby 2.6 it has a second alias namedfilter
, thus making the only Ruby method I can think of in this elite category. As a reminder here’s how the methods behave:(1..10).find_all { |i| i % 3 == 0 } #=> [3, 6, 9] [1,2,3,4,5].select { |num| num.even? } #=> [2, 4] [:foo, :bar].filter { |x| x == :foo } #=> [:foo]
I think today
select
is the most popular of the 3, but I can imagine that over timefilter
will give it a run for its money.1The usage of aliases at this level has always been a bit controversial in the Ruby community. On one hand you make it easier for people coming from different backgrounds to find familiar methods, but on another you also confuse people a bit, as it’s not obvious that the different names are aliases. I’ve seen plenty of people asking what’s the difference between
inject
andreduce
and so on.What’s your preferred method to filter elements? Can you think of any other methods in the Ruby core classes that have multiple aliases?2 Please, share those in the comments!
That’s all I have for you today. Keep hacking!
P.S. As someone commented,
Hash#include?
is even more curious (weird?) as it has 3 (!!!) aliases:has_key?
key?
member?
Will we be able to top this?
Weird Ruby: Incrementing Strings
I guess most Rubyists know that you can use the methods
Integer#succ
1 and its aliasInteger#next
to increment a number. E.g.:1.succ # => 2
The method is rarely used directly, but it’s used internally by ranges to compute the elements within the range boundaries:
(1..10).to_a # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
It’s not so widely known that strings also implement
succ
. The official docs describeString#succ
like this:Returns the successor to str. The successor is calculated by incrementing characters starting from the rightmost alphanumeric (or the rightmost character if there are no alphanumerics) in the string. Incrementing a digit always results in another digit, and incrementing a letter results in another letter of the same case. Incrementing nonalphanumerics uses the underlying character set’s collating sequence.
If the increment generates a “carry,” the character to the left of it is incremented. This process repeats until there is no carry, adding an additional character if necessary.
In more practical terms:
"abcd".succ #=> "abce" "THX1138".succ #=> "THX1139" "<<koala>>".succ #=> "<<koalb>>" "1999zzz".succ #=> "2000aaa" "ZZZ9999".succ #=> "AAAA0000" "***".succ #=> "**+"
Cool stuff, right? I know what you’re thinking now - that’d be really handy for working with numbers represented as strings! Especially version strings! I’m sad to break it to you, but that’s probably not a very good idea. Just take a look at the following example:
# looking good ('1.0'..'1.5').to_a # => ["1.0", "1.1", "1.2", "1.3", "1.4", "1.5"] # oh, shit! ('1.0'..'1.10').to_a # => ["1.0", # "1.1", # "1.2", # "1.3", # "1.4", # "1.5", # "1.6", # "1.7", # "1.8", # "1.9", # "2.0", # "2.1", # "2.2", # "2.3", # "2.4", # "2.5", # "2.6", # "2.7", # "2.8", # "2.9", # "3.0", # "3.1", # "3.2", # "3.3", # "3.4", # "3.5", # "3.6", # "3.7", # "3.8", # ... # "99.9"]
Oh, well… I guess this wasn’t meant to be. By the way, did I mention that successive strings get extra weird if you’re dealing with text that’s not in English:
# Examples in Bulgarian with Cyrilic letters below # Looking good! '1999б'.succ # => "1999в" # WAT!!! '1999я'.succ # => "1999ѐ"
I guess not everyone will understand those examples, but let’s say that most people would expect a different letter to appear after “я”, which is normally the last letter in the alphabets of some Slavic languages. Of course, this has less to do with Ruby and more to do with Unicode and the character ordering there.
Generating successive strings has its uses (mostly in code golfing), but it’s probably not something you’ll see often in Ruby applications. If you know any cool and useful applications - please share those in the comments!
That’s all I have for you today. Keep Ruby weird!
-
succ
stands for “successor”. ↩
-
Weird Ruby: Fun with String#split
String#split
is a pretty-well known and commonly used method. Still, its behaviour in some cases might surprise you:irb(main):001:0> 'foo.bar'.split('.') => ["foo", "bar"] irb(main):002:0> '...'.split('.') => [] irb(main):003:0> 'foo...'.split('.') => ["foo"] irb(main):004:0> 'foo'.split('.') => ["foo"] irb(main):005:0> ''.split('.') => []
No comment needed. Keep Ruby weird!
Weird Ruby: Nil Conversions
Most Rubyists probably know that
nil
in Ruby is an instance of the singleton class NilClass. Becausenil
is a object like any other, we can actually invoke methods on it, and it turns out thatNilClass
provides a few - mostly conversion methods to other Ruby types. Let’s see some of those in action:irb(main):001:0> nil.to_h => {} irb(main):002:0> nil.to_a => [] irb(main):003:0> nil.to_s => "" irb(main):004:0> nil.to_f => 0.0 irb(main):005:0> nil.to_i => 0 irb(main):006:0> nil.to_c => (0+0i)
With those methods you can literally create something (e.g. an empty hash) out of nothing! Whether you should actually be doing this is a totally different matter, though. According to the class docs:
The converter methods are carrying the concept of nullity to other classes.
I’m not sure how this “concept of nullity” is defined, but I’ve always found it weird that you can equate the absence of value with some anchor value (e.g. an empty collection or 0). I know there are contexts in which this may be useful, but I’m fairly certain you’d be able to get similar results without resorting to such conversions.
Keep in mind that you can do similar conversions in a different way:
irb(main):001:0> Array(nil) => [] irb(main):002:0> Hash(nil) => {} irb(main):003:0> Integer(nil) # `Integer': can't convert nil into Integer (TypeError) irb(main):004:0> Float(nil) # `Float': can't convert nil into Float (TypeError)
Notice how the “safe” number conversion methods
Integer
andFloat
operate differently and raise an exception when passednil
. That’s part of the reason why it’s often suggested to avoid the use ofto_i
andto_f
to avoid converting unexpected values.The Zen of Python famously says:
Explicit is better than implicit.
Errors should never pass silently.
In the face of ambiguity, refuse the temptation to guess.
But in the land of Ruby it seems we like to live dangerously!
That’s all I have for you today. Keep Ruby weird!
Subscribe via RSS | View Older Posts