I am working as a staff engineer in big production codebases (with all the traits of those: some code is good, some is bad, some is unrecognizable legacy, sometimes we are in a hurry and write awful code, sometimes we have time for refactoring etc.)
And my observations about the logic and perception of the features are drawn from the practice of code reviews, mentoring new people, and discussing ways of solving tasks in this environment. Obviously, I am frequently a driver of new code practices, but I am also trying to be a good person and a good colleague and notice how comfortable people are with various parts of the codebase, various idioms, etc.
One of the main topics of this article series is uncovering the language's logic and intuitions (to stop perceiving it as a "bag of random syntactic features you need to learn or should guess") and using those intuitions for code that is, yes, better for the reader, but the code that can be created in a quickly-changing production environment and rewritten fearlessly.
I'm not objecting to your article, but to this language and its (you called it!) "useless Ruby sugars". People sometimes use humor to mask suppressed pain, I've noticed.
A smart individual can adapt to any environment and its quirks and thrive. But some of their capacity always goes towards serving the "quirks adaptations". It's kind of like GPT. It's a smart bot, and if you ask it to answer a math puzzle by writing backwards in Romanian haikus - it will. But the effort to make haikus in backwards Romanian make its math puzzle answer that much more likely to be wrong.
I have no shadow of a doubt that people who are used to Ruby know every shade of gray in how to forward arguments through byzantine syntax. Much like PHP programmers have zero issues with every function in stdlib randomly switching needle/haystack parameter order, or remembering which identifiers are case sensitive and which aren't, and the mix of conditions where an identifier needs a $ in front or doesn't need it. Or which one of 10 different stdlib APIs doing kind of the same, but in different state of deprecation and introduction they should use. Or the fact PHP has a type system, but not for the most common data type (array), and there are two modes of validating parameters (strict and non-strict) which can be switched on and off per file and bomb on you unexpectedly when combining both, despite your code is logically correct in isolation. It's trivial stuff to them. They can juggle all this and more. But someone coming from outside would take one look and say "how about no". Because PHP for all its advancements is not a good language.
And Ruby is also not a good language. In fact, it's clearly getting worse over time.
Thanks, that's the highest praise I could possibly hope for :)
It is one of the things I always enjoyed and tried to achieve myself (when the discussion of design decisions in one language would provide food for thought in the context of other languages/domains).
Well, unfortunately a lot of new stuff becomes controversial on the pure ground of "no new language changes! it is good enough for me!"
TBH, I thought about a post series like this for a long time, but the last trigger was an announce of a special Rubocop addon to "disable useless syntax sugar"[1], and pattern matching was one of the "useless sugars" in the list. So I felt I need to cover it, too. And in the end of the day, I think it is an interesting excercise to analyse it in the same way as other, less significant and more controversial, features (the most intresting stuff would be in the second part, though).
I wasn't aware of that new cop. Anecdotally, find Rubocop (and Flog and Reek) to be backwards tools. To me, they pattern match (ha) as the same core problems with the Ruby ecosystem. Ignore best practices that other language ecosystems have evolved, and embrace what other languages agree are bad practice.
I'm glad I missed that r/ruby thread. I wouldn't have been very nice. I honestly can't tell if it's a troll or not. I love all of those features of Ruby, and use them daily.
Ruby didn't always had both. `reduce` as an alias for `inject` was introduced in Ruby 1.8.7; `filter` as late as in Ruby 2.6 (just a few years ago, and most of the codebases I saw are still preferring `select`).
Yes, the dictionary is normalized in the last years towards what became industry/mainstream standard names for the concepts, but the statement that initial names were derived from Smalltalk is correct.
Yeah, should've mentioned that probably (though a post is already a mess of footnotes, aside notes, and parenthicals).
I do believe though that a lot of modern languages got that from Ruby (or, via Ruby, from "JS that Rubyists wrote" and such), when it was a "hot new thing" for a brief moment in 2000s. Or maybe it is my skewed perspective.
I believe it is a habit back from my journalistic days, when there were "insets" besides the main text with "things that deserve mentioning here, but kinda out of the main flow... but more related to it than footnotes." I miss the richness of markup of paper magazines!
Does it harm the reading experience significantly?
Well, first of all, I was one of those who fought for method references as first-class objects for a few years (the story is told in my blog before[1]), and "let Ruby eventually become more functional" was one of my main arguments!
That being said, there are a few problems on this road, that make it less probable to be followed. First, as Ruby doesn't have first-class Method objects (e.g. object corresponding to `File.method(:read)` doesn't exist until you call `method(:name)` explicitly), this style being popularized will either bring a lot of subtle performance problems, or will require redesign of Ruby's internals.
Second, if we'll make an example _a bit_ more complicated, say:
...it would be impossible to rewrite with `method`, until currying would be signficantly improved (currently it can only be applied to first arguments, not tail ones, and even then looks horrible). So it is not impossible, and it is a turn of the language I'd like to see in the future, but it is not as straightforward as I once imagined.
> I don't find the hypothetical Ruby syntax ["hello","goodbye"].map(&reverse) to be offensive or wildly inconsistent.
This syntax wouldn't work in Ruby, because bare `reverse` is already a method call, not a reference to a method by name. Allowing method calls without parentheses is crucial to Ruby's design, where all objects are fully opaque, and every `obj.attr` looking like a getter, is just a call to an instance method `attr`. (This is, as far as I understand, the opposite to similar languages like Python/JS, where the object is a dictionary of attributes, and `obj.method` reference the attribute of type "method", while `obj.method()` is an invokation.)
This design became a huge drawback in the age of functional(ish) programming, because the simplest way for refer to a method in Ruby is `method(:its_name)` (which is also inefficient, because it creates wrapping object of class Method on the fly, there is no pre-existing first-class object), and any attempt of passing/combining methods would be cumbersome due to it.
...which is semantically cool, but looks ridiculous.
Another important trait of Ruby's design is that most of the important methods belong to their first argument, so it is not `reverse(string)` but `string.reverse`, so even to obtain a reference to a method, you need to have an object it belongs to! So you can't do that:
method(:>).call(a, b)
...because all operators are called on their first operands, so you need this this:
a.method(:>).call(b)
...which will require to refer to at least a first argument of the operator, so you can't produce "argument-less comparison operation to call later".
This is the sort of thing I meant by "essentially immiscible". At a 30,000 foot view, yeah, it looks like lots of languages could "just" add currying.
But when you get down into the weeds of what it means, it turns out to be something you can't "just" drop in. You really need to build the syntax for it from day one.
> you can't produce "argument-less comparison operation to call later".
You absolutely can, if you know the specific one (that is, type) you need.
E.g.,
gt = Integer.instance_method(:>).method(:bind_call)
# gt is a two arg callable functionally equivalent to ->(a,b) { a < b } for Integer a, Any b
Of course, doing this instead of just doing:
gt = -> { _1 > _2 }
loses Ruby duck-typing and dynamism with regard to the first parameter; which may be acceptable in some cases, but not others. Can you do the general form without an explicit lambda? Probably there is a way, but its not easy.
I made a small nod towards Haskell's way of doing things in the last paragraph of "How others do it" section:
> We might also start look into the concept of the “tacit programming”, which, from some point of view, is also about “not repeating the arguments,” but this would be a much longer post—while it is obscenely long already.
Note though that my analysis was mostly about "how you would do that in an established language," not "how would I design a language from scratch." And Ruby is very different from Haskell in its syntax, semantics, and typical intuitions of the language writer and reader.
So designing a solution for clearer representation of "default parameters" is quite different in a language which is built around an `object.method(argument) { block }` as its atomic phrase, and one built around `function(arguments)`. (This can turn into a discussion of which phrase structure is more elegant in general... But I believe it was already done quite a few times in a history of programming languages evolution!)
I don't think this corresponds to my experience.
I am working as a staff engineer in big production codebases (with all the traits of those: some code is good, some is bad, some is unrecognizable legacy, sometimes we are in a hurry and write awful code, sometimes we have time for refactoring etc.)
And my observations about the logic and perception of the features are drawn from the practice of code reviews, mentoring new people, and discussing ways of solving tasks in this environment. Obviously, I am frequently a driver of new code practices, but I am also trying to be a good person and a good colleague and notice how comfortable people are with various parts of the codebase, various idioms, etc.
One of the main topics of this article series is uncovering the language's logic and intuitions (to stop perceiving it as a "bag of random syntactic features you need to learn or should guess") and using those intuitions for code that is, yes, better for the reader, but the code that can be created in a quickly-changing production environment and rewritten fearlessly.