To be completely honest, I think I have some counterarguments to the current conclusions.
I believe the code that looks "logically possible" should be "physically possible" (unless there is a good argument why it is impossible, like the ambiguity of parsing).
In the case of allowing forwarding in blocks, I find two questions below (with their answers) to be important.
Would this create new types of confusion?
I believe it would not.
-
def m(*); ->(*) { p(*) };
, if it would forward the proc args MIGHT BE considered confusing. But it is confusing in EXACTLY the same way as def m(a); ->(a) { p(a) };
; no new complication to explain here. Such cases with named variables are easily caught by the linter if the user wants (and doesn't stand in the way of quick experimenting).
- but both current behavior (treating it as forwarding method's args) and proposed behaviors (prohibit some of the cases for the sake of disallowing potentially confusing code) would create new types of confusion. Procs/lambdas are Ruby's main functional construct, and the main intuition is that you can do with them what you can do with methods. Breaking this intuition creates learning impediments and falls into a mental bag of "weird language quirks you just need to remember."
Are there types of code where forwarding in procs/lambdas is necessary?
I believe there are. One of the important drivers of ...
design was considering method_missing
/send
, i.e., metaprogramming.
One of the main types of metaprogramming is define_method
, which the author frequently wants to be short and expressive and allow to do everything that regular def
does.
The "start with method_missing
, and then switch to define_method
when the code matures" is one of the widespread approaches to designing various DSLs, and at that moment, the author is in a weird situation:
# code I had
def method_missing(name, **)
if ALLOWED_OPERATIONS.key?(name)
ALLOWED_OPERATIONS[name].call(**)
else
super
end
end
# code I want to replace it with:
ALLOWED_OPERATIONS.each do |name, op|
define_method(name) { |**| op.call(**) }
end
...but for some reason, I can not.
I think it feels like a bug and nothing else. And for relatively new syntax features (which meet some pushback from the community anyway), such artificial limitations might lower the adoption: "Well, I tried it once, it never worked the way I wanted it to. Don't remember the exact reason, but it was confusing, so I don't use it anymore and recommend everyone to do the same."
(Very infrequently such cases would be posted to the bug tracker/discussed in public for the core team to notice that there was such need.)