Settings

Theme

New Features in Ruby 2.4

blog.blockscore.com

222 points by jbackus 9 years ago · 75 comments

Reader

saghm 9 years ago

Some of these are pretty amazing, like the `digits` method and the new OptionParser functionality (which seems to be a sort of standard library equivalent to doctopt!).

That being said, I can't help but shake my head at the section about "multiple assignment of conditionals:

"You can now assign multiple variables within a conditional...You probably shouldn’t do that though."

Am I alone and thinking that it's a little bit hypocritical to specifically add functionality to your language if you don't want people to use it? Or is this just a joke that's gone over my head?

  • epidemian 9 years ago

    > Am I alone and thinking that it's a little bit hypocritical to specifically add functionality to your language if you don't want people to use it?

    It's just a change to make the language more consistent. `a, b = something` already worked as an expression in most places, so it makes sense that it should be allowed on `if` conditions too. Before 2.4:

      > if (a, b = nil) then :foo else :bar end
      SyntaxError: (eval):2: multiple assignment in conditional
    
    So instead of having that special syntax error, and probably needing a justification for it, Ruby 2.4 makes `a, b = something` a valid expression there and removes an edge case.

    That you probably shouldn't use multiple assignment on an `if` condition is a separate issue. It's just a preference, and it follows the same logic of avoiding assignments on conditional expressions in general.

  • jbackusOP 9 years ago

    Ah well "You probably shouldn’t do that though" is just my opinion as the author. I didn't play a part in these changes. I just like Ruby enough to dig into their changelog :)

    • psyklic 9 years ago

      Interestingly, assignment inside conditionals is usually avoided because it is easy to accidentally do an assignment (=) rather than a comparison (==). Note that for multiple assignment this mistake can no longer happen:

        irb(main): a, b == 3
        SyntaxError: syntax error, unexpected ==, expecting '='
      
      Should it still be avoided? :)
      • jbackusOP 9 years ago

        First of all that is an interesting point. Given that it isn't as potentially dangerous I'll agree that it is less bad than I initially thought.

        I'll respond to your question by asking you a question. Which of the following expressions evaluate to "truthy"?

            'truthy' if (a, b = [])              # =>
            'truthy' if (a, b = nil)             # =>
            'truthy' if (a, b = [nil])           # =>
            'truthy' if (a, b = [nil, nil])      # =>
            'truthy' if (a, b = [false])         # =>
            'truthy' if (a, b = [false, false])  # =>
            'truthy' if (a, b = [true, false])   # =>
            'truthy' if (a, b = [false, true])   # =>
            'truthy' if (a, b = *[])             # =>
            'truthy' if (a, b = *nil)            # =>
            'truthy' if (a, b = *[nil])          # =>
            'truthy' if (a, b = *[nil, nil])     # =>
            'truthy' if (a, b = *[false])        # =>
            'truthy' if (a, b = *[false, false]) # =>
            'truthy' if (a, b = *[true, false])  # =>
            'truthy' if (a, b = *[false, true])  # =>
        
        The answer is in this gist: https://gist.github.com/backus/c9b70dee67470698fd7d4a66ddf03.... Don't peek!
        • psyklic 9 years ago

          Good exercise! So it may still be too easy to misunderstand when exactly the condition will be true.

    • saghm 9 years ago

      Ah, okay, I confused this with an official recommendation. Thanks for the clarification!

  • yxhuvud 9 years ago

    It is usually not a great idea, but there are some patterns where it produces code that is a lot nicer than not. In practice, this will allow stuff like

        while x, y = foo.pop
          ..
        end
    
    which without the mass assignment will require duplication of the assignment line.
  • elcapitan 9 years ago

    I like the feature, but just for understanding, will the assignment always be truthy if any of the assigned variables are set? The example is

        branch1 =
          if (foo, bar = %w[foo bar])
            'truthy'
          else
            'falsey'
          end
    
        branch2 =
          if (foo, bar = nil)
            'truthy'
          else
            'falsey'
          end
    
        branch1 # => "truthy"
        branch2 # => "falsey"
    
    
    What about

        branch3 =
          if (foo, bar = ['foo', nil])
            'truthy'
          else
            'falsey'
          end
    
    ?
    • dlisboa 9 years ago

      Haven't tried it, but the assignment semantics is that the return of the assignment expression is always the right hand side of the operator. They probably kept that semantic. So

        foo, bar = ['foo', nil]
      
      evaluates to

         ['foo', nil]
      
      which is truthy.

      If the code was:

        if (foo, bar = [nil, nil])
          'truthy'
        else
          'falsey'
        end
      
      It'd still be truthy even if no variables were "set" (they were actually set, explicitly to nil, but you get the idea) because [nil, nil] is truthy. It doesn't have anything to do with the values of the variables after the assignment, just what's on the right hand side.
      • elcapitan 9 years ago

        Interesting. In particular the result with both values being nil is something I would find pretty counter-intuitive.

        • dlisboa 9 years ago

          Just gotta remember there's nothing special about there being an assignment there, it's just another expression. `if` is another expression and it doesn't care about the status of assignments or whatever is in the conditional clause, only what the expression inside evaluates to.

          As far as the assignment operator evaluating to the right hand side, it'd be more difficult to understand if that wasn't the case. There are more scenarios to destructuring on the left hand side than the right.

          Also keep in mind there's no such thing as a "failed" assignment. They always work, sometimes things are just assigned to nil explicitly or implicitly.

          This case is somewhat hard to get because it brings together a few things of Ruby in way that can get weird. Those things separately are actually pretty good, but bunched up makes it a bit more dense semantically. Which is why it's best to avoid getting too clever with assignments in conditionals.

  • Rafert 9 years ago

    It has a historical reason: https://bugs.ruby-lang.org/issues/10617

    • psyklic 9 years ago

      To summarize* and elaborate: Originally in Ruby, multiple assignment inside a conditional caused a parse error. Why? Because multiple assignment originally always returned an array:

        irb(main): a, b = nil
        => [nil]
      
      In Ruby, non-empty arrays always evaluate to truthy. (The array above has one element, nil.) Hence, multiple assignment inside a conditional would have been meaningless. For this reason, Ruby threw a parse error.

      As of Ruby 1.9, multiple assignment in a conditional now returns whatever the right-hand side of the assignment operator evaluates to. So, it makes sense to re-allow multiple assignment inside conditionals and remove the parse error.

      Here are some examples illustrating the return value of multiple assignment. The first two are truthy while the third is falsey:

        irb(main): a, b = 8, 7
        => [8, 7]
      
        irb(main): a, b = 8
        => 8
        (a = 8, b = nil)
      
        irb(main): a, b = nil
        => nil
        (a = nil, b = nil)
      
      - The above based on user 'bug hit' who attributes Yusuke Endoh, from https://bugs.ruby-lang.org/issues/10617
      • EdHominem 9 years ago

        puts "true" if []

        An empty array is still an object, so tests to true. Similarly 0, etc.

        The only false values are 'false', of course, and 'nil', the lack of anything.

  • brightball 9 years ago

    Let's not forget when PHP added GOTO recently.

windor 9 years ago

Ruby is the first language I really like. The core concept of the language is small, just not easy to get, but the change on how to programming is significant. Sadly, Ruby is not the language for the multi-core world, and many advantages of using it is disappearing. Although Ruby is changing, 2.3, 2.4, maybe 3.0, but what's the difference?

  • tinco 9 years ago

    First of all, Ruby has a very ambitious goal for version 3.0, which most likely involves some kind of thread parallelisation.

    Second, I would not say Ruby is not a language for the multi-core world. There are tens of thousands Ruby production environments over the world that make excellent use of multi-core machines. They run Ruby on problems that are embarassingly parallel such as web applications using process managing application servers such as Phusion Passenger or Unicorn. This is the way many modern web application platforms parallelize, including Node.JS and Python.

    Third lack of paralellization is not a language feature. Both JRuby and Rubininus support threads without a GIL. Projects like Celluloid and Concurrent-Ruby make use of this.

    • windor 9 years ago

      1. Ruby will support parallelization, it’s just a matter of time.

      2.Passenger and Unicorn are multi-process, which due to developers can make use of nearly nothing in Ruby, and that's why gems like https://github.com/grosser/parallel come out.

      3. The language implementation detail matters a lot. Too many gems are not thread-safe. JRuby is an option, but not that good, as there're too many other options under JVM platform. Rubininus is awesome, I wish it comes out earlier.

  • xutopia 9 years ago

    You should check out Elixir. It is heavily inspired by the Ruby language yet makes concurrency easier than many other languages.

  • dragonwriter 9 years ago

    > Sadly, Ruby is not the language for the multi-core world

    MRI isn't the implementation for the multi-core world, but MRI isn't the only Ruby implementation; on language features, JRuby, particularly, isn't particularly far behind (the current version runs Ruby 2.3 code; I wouldn't expect 2.4 support to be that far behind), and fully supports thread-based parallelism.

  • RVuRnvbM2e 9 years ago
    • windor 9 years ago

      Yes, from the beginning, and there exists celluloid which try to implement actor model. The fact that concurrent-ruby cannot be truly parallelized just disappoints me. The same idea of Future,Promise applied to Ruby doesn't gain the benefit. At last, it's just a method of writing 'better' code, not getting better solution.

    • Meegul 9 years ago

      Does this fix the issue that is the GIL?

  • pizza234 9 years ago

    > Ruby is not the language for the multi-core world, and many advantages of using it is disappearing

    Not really; it depends on the audience. The Ruby audience is still mainly Rails development, whose paradigm is multi-process.

    • windor 9 years ago

      Rails is an excellent framework, but it isn't a feature of Ruby. When some other frameworks are rising, Ruby can lose quickly.

  • gabrielc 9 years ago

    maybe you should try crystal.

jewbacca 9 years ago

> If you are calling #sum on an array of non-integers then you need to provide your own initial value

Why? This doesn't seem necessary, but, more importantly, would be inconsistent with #inject's behaviour (if enforced).

----

http://ruby-doc.org/core-2.3.1/Enumerable.html#method-i-inje...:

If you do not explicitly specify an initial value for memo, then the first element of collection is used as the initial value of memo

  • tomstuart 9 years ago

    I assume (but don't know) that this is an attempt to avoid returning `nil` for empty collections: `[].inject(:+)` is `nil`, but `[].sum` is `0`.

    Forcing the use of the additive identity every time, rather than starting with the first element of the collection, is a good way of setting people up to get the correct behaviour on empty collections, even if they don't test for that case.

Olap84 9 years ago

Will this be the last release before 3? Is there a defined schedule for 3 yet? Or just "When it's done"?

ukoki 9 years ago

digits method just saved code-golfers 20 characters.

`num.to_s.split(//).map(&:to_i)` => `num.digits`

  • level3 9 years ago

    As a golfer, that code would have already been golfed down to:

    num.to_s.chars.map &:hex

    So digits only saves 14 characters (22 if reverse is really necessary). But usually we would actually be doing something with the digits instead of just getting them, so the method may save even less.

    For example, for finding digit sums, the new digits and sum methods give us the efficient:

    num.digits.sum

    But for numbers up to 5 digits (or digit sums up to 47) we can already do:

    num.to_s.sum%48

    which is just 1 character longer.

  • xentronium 9 years ago

    It's also reverse.

mark_l_watson 9 years ago

Great to see my favorite language evolving! I also use various lisps, Java, Haskell, Typescript, JavaScript, Python, etc., but I am happiest when coding in Ruby.

Unfortunately, most of what I do now is machine learning, and Python wrappers for underlying C++ code have by far more support than Ruby. (Ruby does have some great ML projects and libraries though).

sergiotapia 9 years ago

Ruby 2.4 adds a new #match? method for regular expressions which is three times faster than any Regexp method in Ruby 2.3:

Why does Ruby have 4 different regexp match functions?

Regexp#match?: 2630002.5 i/s Regexp#===: 872217.5 i/s - 3.02x slower Regexp#=~: 859713.0 i/s - 3.06x slower Regexp#match: 539361.3 i/s - 4.88x slower

  • Olap84 9 years ago

    So there is choice of course.

    match returns match data, and sets the $~ variable

    === returns true or false, setting the $~ variable

    =~ returns integer (position) or nil, setting $~

    The newest one match? returns a boolean, not setting $~

pmarreck 9 years ago

Ruby continues to serve as a continuously fresh inspiration for Elixir. ;)

toolz 9 years ago

Why do the old match operators now implicitly set a global variable? I feel like in a year I'm going to be debugging some really weird race conditions because of that change and I can't see any value in it...if I want to use the value of a match operator I'll _always_ save it to a local variable.

Can anyone shed any light on this, because my first attempts at grok'ing this change have me really blown away.

  • Lukas_Skywalker 9 years ago

    They have been setting the global variable before. This is not a new behaviour. I think it was to match Perls behaviour iirc

    • toolz 9 years ago

      Ahh, thanks for the clarification I had no idea this was old behavior but it looks like the new method might allow them to deprecate what I would consider an anti-feature later.

      • anamoulous 9 years ago

        This is the kind of stuff that is great for shell scripting / one liners. I doubt they are ever going deprecate it. Maybe the best thing is to rewind your mind 30 minutes and pretend you never read about it : )

        Example

            ruby -ne 'puts $1 if $_ =~ /^(([\d\.])+)/' < /etc/hosts
        • toolz 9 years ago

          I knew things would make sense the more I learned about it. Thanks, TIL. Now I'll pretend I never knew about this so I'm never tempted to abuse it.

  • pmarreck 9 years ago

    Ruby has ALWAYS set certain $global_vars based on a match. This borrowed (directly) something that Perl did/does, as well. This is not a new behavior. See docs: http://ruby-doc.org/core-2.2.3/doc/globals_rdoc.html for "global", "predefined" or "magic" variables. That documentation has existed for many, many versions (not just 2.2.3, per the URL).

    If you have issue with this sort of thing, you should go to Elixir. ;)

  • josho 9 years ago

    These globals are thread local and method local, so these are safe from race conditions.

    Though, I too had to dig into it the first time I saw use of these special vars to gain confidence that I wasn't introducing a world of pain when we hit load.

proyb2 9 years ago

This code doesn't make sense.

    123.digits                  # => [3, 2, 1]
  • gollahon 9 years ago

    Usually when I've seen digits indexed in a number it occurs from right to left (certainly in binary contexts--bit 0 is the least significant bit and bit 31 is the the most significant bit in a 32 bit integer). So, 123.digits[0] means the least significant digit, 3, whereas 123.digits[2] would give you 1, the most significant digit.

  • pmarreck 9 years ago

    The importance of the parts of a number matter in order of https://en.wikipedia.org/wiki/Significant_figures

    So this ordering is least to most significant.

dkarapetyan 9 years ago

These are all great but when are we getting optional typing? If the js ecosystem wasn't a clown operation my language of choice would be typescript instead of ruby.

  • nazgob 9 years ago

    It's not on agenda and it will never happen. There are so many languages with fine typing, why break a dynamic one by adding stuff to it?

    • sundarurfriend 9 years ago

      > It's not on agenda and it will never happen.

      I believe Matz has said that it is on the agenda for Ruby 3.0: that it will have static typing and that it will be optional.

      • nazgob 9 years ago

        TIL, I guess that making Ruby 3.0 3x faster (3x3 initiative) and adding optional static typing at the same time might be quite hard.

    • endgame 9 years ago

      I think the person you are replying to will disagree that it is "breaking" the language.

  • pmarreck 9 years ago

    If you like types, why would you use TypeScript over Elm? http://elm-lang.org/

    • dkarapetyan 9 years ago

      Because I like optional types. I don't like the type system dictating the entire design.

      • pmarreck 9 years ago

        I like optional types as well, but more because I'm lazy. The Elm and Haskell people are not complaining about types, and given that every function takes SOMEthing (of some implicit type) and usually spits out SOMEthing (of some implicit type), I can hardly see how it will "dictate the design" more than create a bit more validation code.

        • dkarapetyan 9 years ago

          Not having this flame war. I have my reasons, you have yours. We will continue writing code using our separate ways. Certain patterns are awkward or even impossible in statically typed languages and you need to bypass the type system to get things done so implicit or not, static type systems have a cost. There is a benefit as well but the kinds of systems I work on I don't need the extra validation. I need the ability to quickly prototype and ship and then layer on the validation, hence the need for optional types.

          • pmarreck 9 years ago

            Didn't think I was starting a flame war, I thought we were having a stimulating discussion, actually, but the two are often confused online unfortunately. I don't use any statically typed languages myself, so there is no reason for me to advocate for them from a personal bias standpoint, I was just saying that the people who DO use them seem to be pretty sold on them. Your criticisms are valid of course!

integrii 9 years ago

Ruby syntax is just getting goofier and goofier.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection