Hacker News new | ask | show | jobs
by saghm 3619 days 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?

6 comments

> 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.

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 :)
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? :)
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!
Good exercise! So it may still be too easy to misunderstand when exactly the condition will be true.
Ah, okay, I confused this with an official recommendation. Thanks for the clarification!
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.
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

?
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.
Interesting. In particular the result with both values being nil is something I would find pretty counter-intuitive.
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.

It has a historical reason: https://bugs.ruby-lang.org/issues/10617
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
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.

Good catch! If only other languages were as simple :D
Let's not forget when PHP added GOTO recently.