I'm a big Ruby fan, or at least I used to be. I had a hard time learning the language, because there are just too many ways to do the same thing. I would always second guess my memory and be forced to look up even the simplest most. common things.
This is a good example of that:
> There is more than one way to call a lambda function:
I'm still a fan of the language, but I hope going forward they tone down the idea of N ways to do the same thing. It also seems to bite the language designers in the butt, as they end up running out of symbols for new concepts.
Did you read Eloquent Ruby? Having many ways to do the same thing is intentional in Ruby's design, to try and promote a coding-as-literature style, whereby the language gives you the tools to write code that is as close to plain English as possible. This is explicitly why Ruby includes "unless" in place of "if not". The more ways that there are to express the same idea, the more tools the programmer has to write code that is easily readable and understandable by others. Human comprehensibility + a comprehensive test suite = long-term maintainability.
E.g. "===" is supported because it's used in case ... when ... matching, and so it allows you to create lambda's that can be used in "when" clauses. You're not really expected to use it to explicitly call a lambda, and using "===" rather than "behind the scenes" in the form of a case/when is rarely done.
Meanwhile "[]" allows you to use a lambda in the same circumstances as an array or hash without writing different code depending on type.
Both are there as part of the Proc interface because they have significant impact on the usability of Proc instances by allowing them to be used in more contexts without special treatment.
Attention paid to things like that is part of what makes Ruby pleasant to use.
".call" vs ".()" is left, and maybe having just one of them would be ok, especially as frankly I can't recall the last time I saw ".()" used on a lambda "in the wild" (I'm sure people are using it, just not in any code bases I've looked at recently), but they serve different purposes than "===" and "[]" even though their effect for Proc instances are the same.
As someone who has been doing Ruby for 20 years, the bbatsov style guide is bonkers.
Rubocop can be good, but it needs too much configuration to make it not follow bbatsov guide — which is simply wrong on a number of levels.
Recently, I’ve started using https://github.com/testdouble/standard as a wrapper around Rubocop. It is both less and more opinionated than Rubocop, but aside from two style choices (`%w[]` — using `[]` there is legal but not idiomatic Ruby IMO; I also prefer terminal dots rather than leading dots (e.g., `foo.\nbar` instead of `foo\n.bar`), it doesn’t bother me nearly as much as Rubocop’s defaults do.
There are many things that Rubocop gets wrong in its defaults. The absolute single #1 thing it gets wrong is markers. It sort-of supports the only useful distinction, laid out by Jim Weirich (`foo {}` when block return values are used such as `[…].map {…}`; `foo do end` for blocks without a meaningful return such as `[…].each do … end`). However, its "semantic" mode does this in the least-useful-way possible, by looking at the method names before the block. It may be the only way that Rubocop can do it, but…
You could be me. Exasperating for someone coming from C/Python/Lisp/other
lambda makes lambdas but not really because you need `call` so you need to know they're a lambda. Ah yeah Proc a bit like lambda but also not really. And not until version 2.blah....
That's because Ruby is really a Smalltalk in disguise :-)
But seriously: Ruby has powerful message passing, blocks, and runtime introspection that address the same niches as real lambdas in other programming languages. They're difficult features to get used to from a Python (or Lisp) background, but leaning into them reveals much of Ruby's hidden beauty.
Lambda makes Proc instances because Ruby is purely OO and every value you can get is an object. It might be nice if there were syntax that allowed you to treat an object as if it was a method, but it'd be tricky because of other Ruby syntax.
> Ah yeah Proc a bit like lambda but also not really.
lambda/-> creates a Proc instance (with an appropriate flag set). The distinction between lambda vs (lower case) "proc" is tricky until you consider that a "proc" is effectively just a shortcut to return a block as a value. The semantics of a block reified into a Proc instance by giving it a name, and a Proc instance created with "proc" are the same.
> lambda makes lambdas but not really because you need `call` so you need to know they're a lambda.
Ruby lambdas are objects and not (potentially anonymous) free functions because Ruby has objects but doesn't have free functions, anonymous or otherwise, and all calls are consistently method calls on objects.
So, it's kind of to throw this complaint on the more than one way to do things pile since this is literally an issue of Ruby has exactly one way to do this particularly thing issue.
I find it amusing because I'm a Rubyist who has been trying to pick up some Python and I have exactly the same problem. There seems to be several ways to do everything (particularly around working with containers, lists, dicts, etc.) and not a lot of consistency as to the approach taken a lot of the time (e.g. functions versus methods on objects).
Agreed. Also a Rubyist that has worked on some fairly large Python codebases and aside from your points (builtins vs object dispatch) I've found certain forms of list comprehension and moreso, decorator abuse, to be some of the most difficult things to deal with at times and as easily on the level as any magic I've encountered in Ruby.
IME, there are relatively few good uses of lambdas in Ruby: blocks can do almost everything that lambdas can, but with better syntax support and closer to the "idiomatic" patterns that most real Ruby uses. For the things that blocks can't do, there are procs (which the article correctly observes are very similar to lambdas).
A good example from the article:
> If you want to map over a collection, but the map function changes based on the input, you can use a lambda to encapsulate the changing logic.
You can do this with `Hash.new { |x| ... }`! The block version is more readable, more idiomatic, and it gives you free memoization.
I wish people would stop seeing blocks as different.
Lambda and proc in Ruby both produce instances of Proc.
That blocks are not Proc to instances in MRI until you name them is an optimization only, and nothing stops an implementation from making blocks Proc instances (indeed my woefully incomplete Ruby compiler does that).
I agree with you that block syntax means you're less likely to need to explicitly pass lambda/procs around, but part of the reason people get confused about this is seeing blocks as something more different than they are.
i just want you to exist in this little do block right here, don't need to name you, don't want anyone else to call you, but you need to do some things
This is a good example of that:
> There is more than one way to call a lambda function:
---I'm still a fan of the language, but I hope going forward they tone down the idea of N ways to do the same thing. It also seems to bite the language designers in the butt, as they end up running out of symbols for new concepts.