Ruby adds experimental support for rightward assignments
blog.saeloun.com
blog.saeloun.com
The craziest part of this is that Ruby does not provide a full featured Ruby parser, so its entire static and dynamic analysis ecosystem depends on a (actually very high quality) 3rd party parser, begrudgingly maintained by someone who (AFAIK) doesn't even write Ruby anymore: https://github.com/whitequark/parser
When I see new language features like this, I think of how Ruby's entire tooling ecosystem depends on the dramatically underfunded (and therefore primarily goodwill) efforts of high output maintainers like whitequark and a few others. Ruby's highly dynamic and untyped nature means these tools are all the non-runtime guarantees you can get, basically. Epitome of digital infrastructure.
Consider asking your company to fund some of these people:
* https://github.com/whitequark (maintains parser)
* https://github.com/sponsors/bbatsov (maintains RuboCop)
* https://github.com/sponsors/mbj (maintains unparser and mutant)
---
As context, I know this stuff intimately because I used to contribute heavily to most static and dynamic analysis tools in the Ruby ecosystem (https://github.com/backus?tab=overview&from=2017-12-01&to=20...) and used to track new ruby changes really closely: https://cognitohq.com/new-features-in-ruby-2-4/
Anyway, I remember just how frustrated whitequark would get every time CRuby decided to make some random syntax change. I have a lot of respect for the current maintainer(s) for putting up with the ever growing complexity of the Ruby syntax. I hope Ruby stops changing the syntax on a regular basis, but I doubt this will happen any time soon.
These changes are always justified with a hand wave and a reference to "developer happiness" and ergonomics. I think that justification is seen as insulting when all the static/dynamic analysis tools obviously actually deliver a lot of value to Ruby devs, and yet the basic tooling to support this ecosystem (robustly parsing, traversing, annotating, rewriting, and unparsing an AST) is not provided by the language.
If you've seen a method one way many times while learning, then see a synonym, you end up having to search up docs to make sure there isn't some subtle difference.
Function f () {
if(errorCindition) return
if(normalBehavior) {
return 1
}
else {
return 2
}
}(I know, my use of braces and lack of semi-colons will cause flame wars!)
The error condition looks different from the rest, so when I’m scanning quickly, I know where I am. My eyesight’s rubbish, so the shape of the code has a marked impact on me. I don’t have to read the whole thing to orient myself.
I thought it was nuts when I read the title, but reading the examples had a noticeable “easing” effect on my reading. So I’m very excited by this addition to Ruby. It could lead to some neat, expressive patterns.
This is an undervalued aspect of development. The tactile shape of a block of code conveys some aspects of intent, and overly aggressive linting tools destroy that intent.
Sounds like the timtowtdi of perl. Funny, I've always called ruby "perl++".
Remember how perl is known to be readable?
Unless is great if it's the first or second clause on the line. If it's the tenth, then it feels more like slapstick and I am the butt of the joke. Haha, just kidding, we aren't really going to call that function.
Similarly, rightward:
someBigFunction(thatNeedsAnswersFrom(), otherFunctions(), true) => variableIAmDebugging
Three unnamed Laws of Computing that everyone ignores and at their peril:- Nobody reads your unit tests in a PR
- People glaze over at the end of long lines of code
- Merges are hard
#2 and #3 combine to cause over half of the bad merges I have had to diagnose. Don't save the important bits for the end of a line. If I were still writing Ruby code, I'd be prepping to see more of them.
I can't control other people. Neither can you. Unfortunately saying that and acting like it's true are two separate things, and so we are chronically making decisions based on an illusion of control that is unhealthy to the point of self-harm. It just sets you up for disappointment and/or antisocial behavior (eg delusions of grandeur)
We put safeties on missiles. We color code and cover emergency power cutoffs in server rooms. We've started putting finger preservation devices on table saws. But code? Well it's your fault for backing into the shutoff button.
"You're using it wrong" = "People will use it wrong." = "You're going to have to work with people who use it wrong" = "You are going to run out of places to hide their bodies and just have to accept it being used wrong."
The question isn't "can it be used right?". Every bad library ever written has been "used right" by at least the author, even if not all the time. The question is "can we live with how often it is going to be used wrong?"
And we put linters on code.
Status Quo Bias is probably why we don't think of existing language features this way.
It's of course true that we're all likely to work with (or even be) incompetent people, but I really doubt individual language features has substantial impact on the damage they cause.
Disclaimer: I'm not a core dev but saw a discussion amongst them about such use cases.
>> 4 + 2
=> 6
>> _
=> 6
>> "abc" + "def"
=> "abcdef"
>> _
=> "abcdef
Adding this kind of syntax for something that's already trivial to do is honestly kind of distressing for me as a Ruby developer who's been around since early 1.8.I still strongly believe that the `key: value` hash syntax was a massive mistake. That's a lost battle, but this is getting ridiculous.
[1]: https://github.com/ruby/irb/blob/master/lib/irb/context.rb#L...
Why does this need to be in the language when both major REPLs already support it? Why must this be yet another alternative way to accomplish the same thing as the way that’s existed since the first days of the language?
There is no argument in favor of this addition. It doesn’t allow anything new, it provides a pointless alternative to the current way of doing things, and the one situation where it’s useful—REPLs—there’s already a feature that renders it completely unnecessary.
Is this true (the 'unnatural' part)? Reading left to right, `age = 42` can be read as "age is 42" which feels perfectly natural in English. `42 => age` would be read as "assign 42 to age"? This feels more awkward to me. I'm not sure I understand why anyone would want Rightward assignment.
It's not the same language, so why should he expect the semantics to be the same?
It's a bit like discovering that inline C in your favourite non-C language isn't actually semantically correct C. If you're a mathematician, then you know, intimately and fluently, how math is spoken and written, so seeing something that looks exactly like "inline math", but isn't, is jarring.
(I don't get angry over it, but it is exactly counter to the extremely delicate and precise way mathematicians train ourselves to think about '='.)
x = x + 1 // assign x to x+1`favorite_color = red' == "Favorite Color is Red"
Which feels more natural than: "Red is Favorite Color"
And I don't see why a statement can't describe a mutation.
Below is an _incredibly_ contrived example. Take note how the `=> final_value` syntax is useful. The alternative is to place a `final_value = ` at the _top_ of the statement. I'll admit: placing the assignment at the top of the method chain helps point out to the reader of the code that an assignment is happening. However, more useful I think is quickly recognizing the flow of data.
"hello"
.chars
.size
.*(10980643.4)
.to_i
.to_s(36)
.upcase
=> final_value
puts(final_value)
# => WORLDIt feels a bit like a new method definition syntax that allows you to put the name after the `end` for... reasons.
set age 42
Its sad to see how they keep adding new, arcane syntax for fairly obscure use cases, seemingly without any real clear justification. I wish Matz would raise the bar on these changes - Ruby is mature and already has a very powerful, complicated syntax that gets tricky for corner cases. I appreciate "more than one way to do it", but not everything needs to have ten ways to do it when the first three ways work fine.
https://tio.run/##K0gtyjH7/7@4NEkhMy8ts8Lq0Gpbu0O7NVQSdRRUgG...
> my $x
(Any)
> 1 ==> $x
(1)
> 2 ==> $x
(2)
> 3 ==> $x
(3)
> $x
[1 2 3]
If ==> were simply assignment, we would expect x's value to be 3.my Int $age;
42 R= $age;
say $age;
But I gotta ask: does it have the correct precedence?
And sure, throwing some superficial math at the problem works. But is that a coincidence?
I guess what I'm asking is: how does Nim know that `=>` is infix? How does it derive the correct precedence? It looks like spooky magic, but Nim is pretty well-designed in my experience: what's going on here?
I expect it's the `
Haskell works the same way.
So the compiler itself just sees the `a = b`, the `b => a` is never visible, therefore precedence isn't germane to the template itself.
Neat.
Not quite. Imagine you have:
3 + b => a
Is that 3 + a = b, or a = 3 + b? Precedence still comes into it. (I don't know nim's solution.)3 + c => d
the left hand side (3 + c) is b, and the right hand side is (d) is a, and this will apply no matter what you put on those sides, so it's rewritten in its entirety before the actual compiler sees the code.
Unless you're talking about a scope violation/dirty macro situation? And yeah I don't know Nim's story around hygiene either, but: it's usually only a problem with Lisp macros, exactly because of the homoiconicity. I'd wager Nim templates have their own scope that won't shadow the rewrite.
foo = 1
bar = 2
foo = bar
?- FOO = 1,
BAR = 2,
FOO = BAR.
false. ?- FOO = BAR, FOO = 1, print(BAR).
1 ?- FOO = 1,
BAR = 1,
FOO = BAR.
FOO = BAR, BAR = 1.> When you type a long expression only to remember at the end that it would be a good idea to save the result, a right-hand arrow allows you to perform an assignment without retyping the line.
(I don't do this personally, though. I just run the expression and then my next command is `x <- .Last.value`)
user.delete() if deleteUser
It is probably thought that this feature would result in similar readability improvements. if deleteUser
user.delete()
end
It's easier for me to read. It doesn't try to hide away logic. And if the line gets longer if the conditional gets more complex or I want to add an else I can without reformatting.return if a.nil? return if attempts < max_attempts
In this case "return if" becomes like a keyword and you can ignore it and focus on the rest of the expression. It also seems very readable for other simple values like `return false if ...`. But I agree, as soon as the code to the left of the `if` gets non-trivial it is harder to read.
Any time I use it, I wish python had just gone with the normal ternary (? :) operator.
No, python writes the ternary operator with words, it doesn't have rightward conditionals.
The key difference is that the else clause is required in the Python ternary, where it's optional in a conditional.
makejunk().filter().last(4) => fourthings
feels better than fourthings = makejunk().filter().last(4) makejunk | grep foo | tail -4 > fourthings
is more popular than > fourthings makejunk | grep foo | tail -4
Having said that, I don’t understand why you would add this, but then, I don’t understand the popularity of ruby or other languages that think adding alternative ways to do things most of the time is a good thing. fourthings=$(makejunk | grep foo | tail -4) a = 2
b = 1
b >= a # false
b >= a => a # assign false to a?
b => a # assign 1 to a?Like natural language (but unlike programming languages constructed with thought toward ease of automated parsing), Ruby syntax is quite context sensitive.
b <= a # true
b <= a => b # assign true to b?
I'm not too thrilled about the hash-arrow - but I think it's a bit more obvious from context.As a description (which is an easier translation of this idiom into English than an imperative) “is assigned to”.
Imperatively, English tends to prefer verb-object with implicit subject, so we ought to be using a Lisp family language (setq “foo” 42) maps better to English imperative structure than most popular languages that do imperative assignment.
"is assigned to" is passive voice. I think we've all learned in writing classes we should avoid it as much as possible because stating the active voice is clearer.
This is true of the indicative mood, but not the imperative.
Giving commands in the indicative mood is not typical.
Writing classes can be misguided.
[1]: https://www.ef.com/ca/english-resources/english-grammar/pass...
"The passive is of course perfectly respectable, and there is no reason to try to avoid it. (To say that it shouldn't be over-used, or it shouldn't be used where it is inappropriate, doesn't distinguish it from any other construction or expression.)"
See the tremendous list of Language Log links at the bottom which deal with the issue from a host of angles. I recommend reading some of them - Geoff Pullum, in particular, is usually delightful.
"You should use active voice whenever possible" is bad advice - both active and passive are (nearly?) always possible but often one is clearer - and that can very much be the passive.
"You should use active except when passive is better" is arguably correct but fails to actually provide guidance about when the passive actually is better - which isn't rare.
That the passive is often overused may be useful to note, if that's actually the case.
In programming languages we often find better, we prefer explicitness over implicitness if the language is already designed to be very concise. Case in point: Python.
The clearest is not the most informative; the clearest provides the most important information and the least distracting information.
"Helicopters were used to control the blaze" is no less clear than "the blaze was controlled using helicopters". And, assuming the news item is about the trajectory of the fire, it is much more clear than "Pilot John Smith, having just moved to the area from Baltimore where he attended school with a 3.7 GPA, helped control the blaze with a fire department helicopter" despite that offering quite a bit more information.
To your explicit claim, I contend that "the passive voice is overused" would not be made clearer phrased "writers overuse the passive voice".
However, you already construct hashes like hash = {:a => b}.
Perhaps we can also add reverse hash construction like hash = {b = a} for consistency (and additional madness)
Quiz: what does the following Ruby code do?
a = b => c = d [$a => $b]->[$b <= $a]
for the minimum. (I think this is from Effective Perl Programming, but I couldn't find it at a glance.) DoThing()
.withSomeOtherThing()
.onSundays()
.withCharset(Blah.BLAH)
.betterAddThis(x -> { ret stuff() })
.build()
=> turducken
The whole assembly of the variable 'turducken' is drifting rightward. By the end we want to get to the point rather than backtrack. DoThing()
.withSomeOtherThing()
.onSundays()
.withCharset(Blah.BLAH)
.betterAddThis(x -> { ret stuff() })
.build()
.into(turducken)
Where a.into(b) is really (into a b), which is a macro sugar for (set b a) and not a function.Is there any reason why they overloaded the hash rocket? Even the simple examples in the article look like somewhat malformed hashes to me!
3 => box_height
[170, 65] => height, weight
traits => height, weight
How does the parser tell them apart?The fact that it's used for a kinda similar purpose in rescue clauses is a factor.
> How does the parser tell them apart?
Unbraced hashes can only occur in last-argument-of-function position, so unless it's naked (not a parenthesized subexpression) in that position, it can't be an unbraced hash.
I'm not saying it's good or bad, I'm just saying that appears to be one of the reasons for it.
Having both options is nice!
I wouldn't even say that they solve different problems for me, it's more a matter of how I need the problem solved. If I need something that runs on other OSes than my Linux box preferably with as few dependencies as possible, Go. However, I can probably code it faster in Ruby, so if it only needs to run on my machine or if I know Ruby's available where it needs to be run, then I'll probably go with Ruby.
I think, instead of arguing which is better, the argument should be about whether they're useful. And for both Go and Ruby that's an "absolutely!" from me.
Eh, there is a reason why Golang has such a huge popularity.
1.2 + 2 | double | abs => result 'think highly' => sentiment
this post.Ah yes, the R language, a model of clarity, beauty and lightness in the world of programming languages. Truly an example to follow.
(And it's not like anyone ever uses that "feature" in the R world either.)
I've used it. The overloading of the equals sign is something that has never made sense for assignment. (Seriously, it's hard to intentionally come up with dumber notation than x = x+1.) I do use -> sometimes. It's more natural to do the evaluation and then figure out where you're going to store the result (particularly when you're programming).
This is minor as these things go. Definitely not a big deal one way or the other.
The `<-` in R comes from the APL keyboard, and that glyph is an assignment in APL. It wasn't just some crazy invention from Ross & Robert or even John Chambers.
The `:=` has been in use in other langs like MySQL
The same problem led early C (pre-K&R1) to change its compound assignment operators from "=op" to "op=". In early C, "x = -3" assigned the value -3 to x (as it does now), but "x=-3" meant "x = x - 3" (now written "x-=3").
-Arguing that adding a feature is ok because "R does it" is comically wrong when one is acquainted with all of R's warts and superfluous stuff
-Not even the R world uses that feature either, so we have actual empirical evidence that it's not that helpful to developers.
It's fine to not like the feature–I don't know enough about its usecase to feel one way of another–but your comment felt unnecessarily negative without adding much to the discussion.
Knowing that R users have access to these syntax and choose not to use it is interesting to consider, but that was overshadowed by your first statement.
You do have the correct sentiment in that for actual code meant to be distributed to other human beings it's almost never a good idea to use it although I've seen use cases, particularly involving Tidyverse constructs, where it arguably makes sense.
$ awk 'BEGIN { "echo foo" | getline var; print var }'
foo