Hacker News new | past | comments | ask | show | jobs | submit login
Ruby's Creed (metaredux.com)
105 points by rlue 5 hours ago | hide | past | web | favorite | 40 comments





Regarding the @ positional-argument syntax: I mean, my use of Ruby dropped considerably years ago, but from a Ruby-philosophy standpoint it looks fantastic to me. Having to constantly make up (usually single-character anyway) names for those parameters in map/filter chains was one of the ugliest parts of the language, especially when there was no actually-good name for things like temporary tuples, and the Smalltalk-derived (I think) syntax for block parameters is so visually heavyweight: { |…| … } { |…| … } { |…| … } where all the stuff in the || is close to useless, as opposed to just { … } { … } { … }. Method references passed with &, while separately useful, are not a good substitute; they're often less readable (to me) because the form of the expression is obscured, and they break down much faster, for instance as soon as you need any single other argument, rather than just when the block gets complex enough that the parameter names become important for readability.

I can appreciate that it moves farther away from “requiring things to be explicit means you get fewer accidental barriers to moving code around or increasing/decreasing complexity”, though, as some of my recent work has been in Java and I've had an eye for that aspect of it. But the next time I do anything in Ruby I'll be remembering that this is there.


I like Kotlins solution for this. If there is only one argument in the lambda/block and the type is clear from the context you don’t have to declare it and you can use the ‘it’ keyword instead.

Wow, that's splendid! In a sense, it's a very nice dual to "this", which is also a keyword everywhere and not something like $0 or something. I wish more languages had this. Or, it.

For clarification, it looks like this

  people.filter { it.age > 42 }

And one parameter is 99% of the cases. If you have more than one, you should probably name them anyway.

Syntax is hard. In general I think that adding a simple numbered argument access for blocks makes sense, and something like swifts $ syntax would look pretty natural, ie:

``` books.map { $0.title } ```

In the Ruby lang discussion on this they go through a dozen different possible syntaxes and they all have issues -- for example the $ is problematic because Ruby currently uses these for global variables.

Even so, IMO using the `@` symbol for this will make the language harder to learn. It's not a very nice thing to have syntax that is almost ambiguous to the human.

``` books.map { @prefix + @1.author } ```

I teach Ruby to a lot of engineers (of all different experience levels), and one of the nice things is that it's so easy to pick up and be productive quickly. But there are bits where it takes some practice, and I'm not looking forward to telling people, "The at sign means it's an instance variable. Well, except if you're in a block and it's @1 or @2 or something, in which case it's a positional argument." Oof.


Ruby 1.9 hash literal syntax was introduced as an evolutionary step toward keyword arguments. If the former didn't exist, the latter would be a special syntax similar to but incompatible with the existing idiom to use a hash in terminal argument position as keyword arguments. That is, without the ability to treat foo(:a => 1, :b => 2) as equivalent to foo(a: 1, b: 2), you end up with two versions of every API -- "hash kwargs" and "real kwargs". And you'd similarly lose the ability for existing APIs to migrate to explicit keyword arguments from hash kwargs.

Would I prefer it if Ruby had started out with Crystal's named argument syntax? Absolutely. But Crystal had the luxury of starting from scratch without any need for backwards compatibility.


I think Rubys vararg syntax is terribly unintuitive and inconsistent though.

I don't necessarily agree but it's a fair argument and well written. It's probably impossible to satisfy everyone; if Matz drops these features the people who want them (and I'm sure they exist) will yell that the language is stagnating compared to Python 3 and Node. If he adds them, we are adding bloat. There is constant pressure to add more stuff to "evolve" the language, hopefully the community steer through this pressure to the right direction.

It's interesting to see that languages try to get features that other languages have.

In Swift they have position argument in block from the start

    users.map{$0.name}
now they want to add Ruby's &:attr feature[1]

    users.map(\.name)
Maybe the best feature user want is the one other languages have. Or this is just Blub Paradox[2] in real life.

[1]: https://github.com/apple/swift-evolution/blob/master/proposa... [2]: http://www.paulgraham.com/avg.html

edit: grammar


They both look ugly to me. Furthermore, there is a cognitive burden. There is nothing to think about in

  users.map{|u| u.name}
I even use to write it more explicitly as

  users.map{|user| user.name}
The short forms

  users.map{&:name}
  users.map{@1.name}
are like "oh wow, what does that mean?" and I've been using Ruby since 2005. And the ugliness...

I don't actually mind the syntax that much, `&` on a symbol just gives you a proc for calling `send` on the first argument. What bothers me is the limitations. If you're going to have special case syntax to avoid having a useless first argument why not go all the way?

Something like this would have made a lot more sense imo:

    users.map { .name.uppercase + '3' }
    numbers.map { * 3 }

Another way to look at it is that cross-pollination is a good measure of the merit of a language feature. If one language invents it another a dozen other popular languages adopt it, it's probably a good idea, and its proliferation is good for everyone.

> In my opinion half-baked micro-optimizations add nothing, but complexity in the long run, and should be avoided. I’m really worried that Ruby has been gradually losing its way and has strayed from its creed.

It seems to me that "Optimizing for programmer happiness" is bound to end up with these half-baked micro-optimizations. The very first example of what the author considers the essence of Ruby is a perfect showcase of that, in my opinion.

Without a criteria that establishes whether or not you've now optimized more or less for programmer happiness, your users will just create blub fortresses that they stay in. Since the entire tagline is based on subjectivity and the users don't have any meaningful foundational principles to appreciate, all they have is their opinions on that feature they don't see why they need.


As a rubyist of 10 years, a few thoughts. I wasn't aware of these upcoming features, but to some extent they make a lot of sense. Except... yeah, those symbol choices.

I agree that @1, @2 etc look totally wrong, and maybe that subjective view will change - but scope-wise, having @-variables that are nothing to do with the current instance would utterly ruin scannability. On the other side, I suspect Rubocop will very quickly react to outlaw it, so no big problem as far as my code goes.

I very rarely use &method(:foo). I find it ugly, and &:foo is more useful in 98% of circumstances. Maybe I would use it if it were shorter - but .:foo seems wrong. I feel like it would, again, be harder to scan.

That said, the more I let it sit in my brain, the less passionately I object.

> Still, I was disappointed when the safe navigation operator as introduced, as it basically acknowledged that it’s fine to return nil

I think this is indicative of the crux of the problem. Optimizing for programmer happiness implies you know what programmers need. Some programmers have no problem working with nils - we've long since accepted them, even embraced them.

The safe navigator pattern almost gave us a way of implementing a null object pattern with literally no work, while screaming out to the world "this method accepts nil, so watch out". I would much prefer code that accepts nil as a fact of life than code that denies its very existence.

I feel that "hacky code" will be well served by these changes, and I think that "production code" will be better served by tight Rubocop rules. So, everyone's happy!


> I very rarely use &method(:foo). I find it ugly, and &:foo is more useful in 98% of circumstances. Maybe I would use it if it were shorter - but .:foo seems wrong. I feel like it would, again, be harder to scan.

Ever since the near-death of the hash rocket syntax have these days been coming as you can already see the same line of thinking coming out of it. Superficially the introduction of the symbolic hash syntax that aligns it with yaml/js and saves a few chars was locally good for readability, yet fundamentally it could not deprecate the hash rocket because if one of your keys is not a literal symbol then you're toast and have to use hash rockets in your code, which means you end up with mixed patterns throughout your code and thus have to 1/ keep two tokenisation branches inside your head and 2/ make decisions about the syntax to use every single time you write a hash, which looks like small things but long term sand in a gearbox grinds it to a stop.

At some point you end up with all those little syntactic sugary things which make sense locally but which overall makes for increasingly heavy cognitive overload and you end up in a tar pit through normalization of deviance.

When Ruby began to take off, people said it was like Perl's heir, only readable. Today as both a Rubyist and a former Perlist, I do long for Perl (and violently enforce readability rules on Ruby projects I own)


The author starts with ` named block parameters`. But Clojure had this. I always Ruby has similar thing. In Clojure you can do `%1` or `%2` similar to `@1` `@2` in Ruby. So I guess the feature were inspired by Clojure. Even Elixir has it. The author is also a Clojure developer so I found it's weird that he hate that Ruby syntax.

However, at the same time, I totally agree that Ruby has so much more thing to work and improve, not to worry about these thing. As a Ruby dev, I don't want a new syntax to catch up with JavaScript where new syntax come up every weeks.

Ruby should focus more on type system, performance. Even some type hint like PHP would be very helpful. Lots of Ruby gem kind of introduce type. Example in Mongoid:

  class Person
    include Mongoid::Document
    field :first_name, type: String
    field :middle_name, type: String
    field :last_name, type: String
  end

I would love to have some type so we can use it directly.

Another weak point of Ruby is its module system :(. Some module system like Python would be great where we can load thing and alias them to avoid conflict.


> In Clojure you can do `%1` or `%2` similar to `@1` `@2` in Ruby

I very much like the feature itself, esp. for one liners you can come up with on a pry prompt. What I very much dislike though is the use of @ which is a glorious hack if there is any since currently @ tokenizably resolves to 'instance variable' in many a brain (and @@ to class variable but those should die since class variables can be implemented as instance variables of an instance of Class which is both much more elegant and less prone to corner cases, but I digress). In fact I'd very much be happy to use %1 as a syntax since that would match the "%1" placeholders in strings right away. "%1" in strings, \1 in regex for backrefs, $1 for regex groups, {%1} in blocks somehow works as a metapattern, but reusing @1 vs @myvar is such a mismatch that it brings a terrible cognitive dissonance (just see e.g how in [1, 2, 3].each { @1 + @I } both @ have different scopes!).

I can see the argument coming (if it did not already happen) that "OMG this is going to special case stuff in the parser so that the % method can be separate from this new syntax! plus this may break existing code!" to which I reply %1 won't ever appear in the same place in the AST as a modulo operator with a right hand integer argument and this is your job as a language implementor to make the dev job easy, and it's crackpot anyway since that will already special case the @ syntax in the parser. Simple is hard, bailing on implementing the simple solution as a language implementor even if it's internally tough is a copout.


I was sad to see => aka "hashrocket" syntax go, and it still slips in sometimes... but it is nice being able to use the same hash syntax in Ruby and JS.

What I'm intrigued by is the author's suggestion that there's more to the change than a relatively minor syntax change. Is there more to the story? I always assumed that both syntaxes are functionally identical.


One can only ever use Symbols as keys in the 1.9 hash literal syntax, whereas complex objects responding to `#hash` can be used as keys in hash rocket literals.

> What I'm intrigued by is the author's suggestion that there's more to the change than a relatively minor syntax change.

I don't remember him saying that about hash literals. All I caught was that he doesn't like how it's limited to expressing only symbol-keyed hashes. Maybe I missed something, though.


I’m a huge fan of all the features this person is complaining about.

I like them too, but only because I've been programming Ruby for a while now, and enjoy picking up new tidbits as I go. If I were new to Ruby (or programming in general), I think this would reasonably constitute feature bloat that would make it a less approachable language.

Also, bear in mind that "this person" is not just any Joe Blow but Bozhidar Batsov, maintainer of Rubocop and (as an author of the Ruby Style Guide) something of an authority in the Ruby community.


But do any of these features progress the language or provide any performance benefits? Most read as syntactic-sugar (I understand Ruby is about developer happiness) and unnecessary.

Personally, even though `.then` resembles A+ promise-like syntax -- I find that it feels more idiomatic than `yeild_self`. I remember Matz talking about this at RubyKaigi, and I wondered how it would work in practice; however, I enjoy that syntax much more than its predecessor.

Addressing some of the author's comments, I would agree with:

- Endless ranges

- The "@" syntax names.each { puts @1 }

I would love to see more dialogue on how these changes _could_ positively affect the language. Also, if these are adopted widely, there will be the inevitable Rubocop rule telling you that it prefers this syntax over the "legacy." I love Rubocop, but sometimes I feel there's no end to the journey to writing 'idomatic' ruby and the continued language sprawl definitely does not help on this front.


ah, that topic... Well, for what its worth, I mostly agree with the author, except I never felt Ruby has made me "happy" more than any other programming language with at least some semi-decent features. Its problems make up for its features and, at the end of the day, it has enough annoying parts to make me angry at times.

The new features, of course, is pointless. It solves no problem, but brings with it the typical downsides of adding a new language feature. It reinvents the wheel, but makes it triangle-shaped, and tells you it's only meant for crash-tests anyway.


> Well, for what its worth, I mostly agree with the author, except I never felt Ruby has made me "happy" more than any other programming language with at least some semi-decent features.

In surveys, Ruby hasn't (to my knowledge) scored highest in developer happiness for a very long time (if ever). Languages that don't tie themselves to a subjective tagline like that do, though. Rust, for example, has a pretty clear direction and mission and has some of the most excited and happy users in every survey I've seen.

Trying to talk about developer happiness as some kind of defining characteristic has always been a bad idea and I think, ironically, you end up with unhappy users by using it as an ideal to make change in your language. I think it only gets worse when the paradigm the language is built on and how it expresses that paradigm is also susceptible to more bugs than the alternatives.

The only real way to optimize for developer happiness is to optimize aggressively for simplicity. The entire foundation of Ruby is a barrier to that and it doesn't have the good tooling, interactivity or introspection of something like Smalltalk to handle that.


This is a good reminder as to why Ruby is often called "the bad parts of Perl." I really appreciate other programming language ecosystems (almost any modern ones besides Ruby) that have abandoned things like this as bad practice, while Ruby continues to double down on it.

That's funny, I usually hear Ruby referred to as "the good parts of Perl" (and Google searches for those phrases suggest the same[1]) -- there's no sigil conjugation, objects are first class, and there are fewer magic variables/idiomatic usages of magic variables. I agree with the author that 2.7's new block syntax is a step in the wrong direction (and that safe navigation/method reference are a little uglier than they need to be), but I think Ruby has otherwise done an exceptionally good job of taking the best parts of Perl and combining them with the best parts of Smalltalk.

[1]: I only get 1 Google result for "the bad parts of perl" and about 1000 for "the good parts of perl".


I definitely see Ruby as Perl's spiritual successor. I would say it's one of the few languages other than Perl that can be considered "write optimized". Ruby makes it very very easy to just write code that just works. Just, a lot of code later you have huge monstrosity of rot that's almost impossible to maintain; I think that happens with all large codebases, Ruby just gets there faster.

> I definitely see Ruby as Perl's spiritual successor.

Matz has said this himself:

* "I chose the name of the language from the jewel name, influenced by Perl. Although I named Ruby rather by coincidence, I later realized that ruby comes right after pearl in several situations, including birthstones (pearl for June, ruby for July) and font sizes (Perl is 5-point, Ruby is 5.5-point). I thought that it only made sense to use Ruby as a name for a scripting language that was newer (and, hopefully, better) than Perl." (https://lingualeo.com/tr/jungle/231625)

* "Ruby inherited the Perl philosophy of having more than one way to do the same thing. I inherited that philosophy from Larry Wall, who is my hero actually." (https://www.artima.com/intv/ruby3.html)


You have a huge monstrosity if you or someone on your team is sloppy. I guess you’ve never seen a Drupal or Joomla codebase before. Or JavaScript projects that pull a myriad of junk dependencies. We have a Perl codebase which is actually quite clean. Mojolicious also helps a lot to keep it nice and tidy.

One of the good parts of Perl that Matz embraced was TMTOWTDI.

https://en.m.wikipedia.org/wiki/There%27s_more_than_one_way_...

The author of this piece seems to simply disagree that this is a feature, and that's a widely held, not unreasonable position. But the described changes are just a further expression of that long held design philosophy.


Hm. Personally, I think there's a big difference between "there's more than one way to do it" and "there are some extra, esoterically unreadable ways to do it".

I definitely don't know enough to be questioning Matz's decisions, but the burden of backwards compatibility means there is a material cost to adding features that end up not getting widely adopted, and if that cost is not accepted by folks like Bozhidar (who must now update Rubocop to recognize this syntax), it gets passed on to the Ruby community in general, who must instead deal with a decaying and fragmented language ecosystem.


Things like this which they only just introduced and you didn't know about until this article?

I'd say the strongest possible interpretation of what stevebmark said would include older additions to the language such as from this section of the article:

---------

Here’s a list of changes and additions that I certainly never needed (or wanted): Ruby 1.9 hash literals Refinements %i literals Rational/Complex literals (2/3r, 2+1i) Endless ranges (1..) Safe navigation operator (&.)


I think what drstewart is getting at is the vagueness of stevebmark's claim:

> I really appreciate other programming language ecosystems... that have abandoned things like this

He doesn't articulate what "things like this" are, and instead leans on bbatsov's critique, though it's not clear how. It seems you're suggesting he means "the particular syntax additions bbatsov doesn't like," but that doesn't make sense, either—you're telling me he means that other language ecosystems have abandoned Ruby 1.9 hash literals, refinements, %i literals, etc.?


Numbered arguments for short lambdas is not a rare language feature, and of them all, Ruby (and Perl) is exactly what I'd expect to have it.

Shouldn't this

    h = Hash.new { @1[@2] = "Go Fish: #{@1}" }
Be

    h = Hash.new { @1[@2] = "Go Fish: #@1" }
For maximum syntax golf?

Or are positional arguments not working the same way as instance variables wrt to interpolation?


As for refinements, the use case I imagine is when you need to be defining a method that dispatches on the type of objects that are not “yours”, especially where that dispatch plausibly needs to be extended with more types by other code. (If it doesn't need to dispatch on type, you can just define a function in a module and be done; if the dispatch never needs to be extended, you can kinda do the same thing, but it's much uglier.) Serialization is the most common case where I've run into this, where objects of different types need to be serialized differently; indeed the https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/Refineme... gives “ToJSON” as an example refinement.

A different way of handling this exists in CLOS: methods are defined on generic functions, which are are namespaced in packages just like class names are, but importantly, the class package and the generic package have no pervasive special relationship: a class doesn't “own” methods. So I can defgeneric extravagant-imperial-licentiousness:freeze and then define methods for it on all sorts of types, and it won't clash with someone else's jawbreakingly-simplistic-obdurate-numbness:freeze method. A consumer of either or both methods can choose to import one of the symbols and/or refer to either or both of them by their full names.

In Ruby, I've observed two “traditional” ways to do this: (1) add an ugly decoration to the method name which can never be elided (which is what I did in some internal projects, and then I added a prettier module function for external consumption that just trampolined to that method); or, seemingly more commonly and much worse, (2) ignore collisions, define whatever name you like on whichever classes you like, assume that you have dominion over your favorite short name and/or that the semantics are “obvious” and universal and that no one will ever load a library that does it some other way on top of yours and thereby break all the code depending on your version, and thereby, when that sometimes happens, land the hapless integrator in the dreaded “monkey patch hell”.

Refinements supposedly provide a better way to evade (2). I didn't get to play with them enough to be sure that they do, and in particular ISTR this might not actually let you have a fresh method name which can also be extended elsewhere without requiring all consumers of the method name to be aware of all the extension refinements, which if so seems like a disaster for serious use. I feel like there's a better mechanism for this hiding somewhere in Ruby design-space, but I don't know what it is.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: