zverok
Joined 6 years ago
Joined 6 years ago
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).
1: https://www.reddit.com/r/ruby/comments/16slc10/announcing_ru...
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.
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?
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:
ENV.fetch('CONFIG_PATH')
.then { File.read(_1, encoding: 'KOI8-U')
.then { JSON.parse(_1, symbolize_names: true) }
.dig('metadata', 'created_at')
.then { Time.new(_1, in: CURRENT_TIMEZONE) }
...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.1: https://zverok.space/blog/2022-01-13-it-evolves.html#taking-...
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.
FWIW, you can do that in Ruby:
(JSON.method(:parse) >> method(:puts)).call("[1, 2, 3]")
...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".> 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!)
But I am afraid it is hard to do something to prevent people communicating with article title on its own. Happens all the time with me, whatever I write about! (I tried boring over-explanatory titles, people still argue with _that_.)
That still wouldn't have solved passing additional args, so maybe { object.method_name(_1, args)} is the next best thing. Though it perceives non-atomic due to wrapping block.
Would've been a bit more concise with method references, almost introduced in 2.7, but alas.
(But, well, people tend to want "something that looks like operator, preferably something that looks exactly like |>" and reject every other possibility)
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.