How to Use Lambdas in Ruby (2020)
scoutapm.com
scoutapm.com
This is a good example of that:
> There is more than one way to call a lambda function:
my_lambda = lambda { puts "hello" }
my_lambda.call
my_lambda.()
my_lambda.[]
my_lambda.===
---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.
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.
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…
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....
Aaargh!
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.
> 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.
So you could implement your own "proc":
def make_proc(&block)
block
end
(in fact the above example is from the Ruby docs)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.
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.
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.