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 than whitequark/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 for whitequark/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 supporting whitequark/parser until we switch to using Prism’s AST format natively - currently we’re using Prism as a backend for whitequark/parser.3

    That’s all I have for you today. Keep hacking!

  • 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 named filter, 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 time filter will give it a run for its money.1

    The 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 and reduce 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?

    1. I believe that filter is the terminology used in most programming languages. 

    2. Enumerable#filter! does not count! ;-) 

  • Weird Ruby: Incrementing Strings

    I guess most Rubyists know that you can use the methods Integer#succ1 and its alias Integer#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 describe String#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!

    1. 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. Because nil is a object like any other, we can actually invoke methods on it, and it turns out that NilClass 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 and Float operate differently and raise an exception when passed nil. That’s part of the reason why it’s often suggested to avoid the use of to_i and to_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