Feature #16260
closedSymbol#to_proc behaves like lambda, but doesn't aknowledge it
Description
Seems that Symbol#to_proc
returns Proc
that has lambda semantics:
proc = :+.to_proc
proc.call(1, 2) # => 3
proc.call([1, 2]) # ArgumentError (wrong number of arguments (given 0, expected 1))
But if you ask...
proc.lambda? # => false
That seems to be an inconsistency, which I'd like to clarify. There are obviously two ways to fix it:
- Make it respond
true
tolambda?
(and mention the semantics in docs) - Make it behave like non-lambda.
The second one seems to produce some useful behavior:
# Currently:
[1, 2].zip([3, 4]).map(&:+) # ArgumentError (wrong number of arguments (given 0, expected 1))
# With non-lambda:
class Symbol
def to_proc
proc { |o, *a| o.send(self, *a) }
end
end
[1, 2].zip([3, 4]).map(&:+) # => [4, 6]
Probably all of it was discussed when Symbol#to_proc
was introduced, but as old NEWS-files doesn't link to tickets/discussions, I can't find the reasoning for current behavior.
Updated by Eregon (Benoit Daloze) about 5 years ago
I think we should just return true for lambda?
.
Proc has extra confusing behavior, e.g., #16166.
Updated by nobu (Nobuyoshi Nakada) about 5 years ago
Updated by nobu (Nobuyoshi Nakada) about 5 years ago
- Status changed from Open to Rejected
As a symbol proc cannot know the method to be invoked, so now I think it cannot be lambda.
In the case :+
, it looks like a lambda, but it is not always true.
Updated by mame (Yusuke Endoh) about 5 years ago
Just curious: How do you want to use the result of lambda?
? Even if it returns true, we may pass an arbitrary number of arguments: lambda {|*a| ... }
. I think that lambda?
is useless except debugging.
Updated by zverok (Victor Shepelev) about 5 years ago
As a symbol proc cannot know the method to be invoked, so now I think it cannot be lambda.
In the case:+
, it looks like a lambda, but it is not always true.
@nobu (Nobuyoshi Nakada), I am not sure I get it right. Can you please show when it is not true?..
For as far as I can understand, there are two distinctions of lambda:
- Its
return
returns from lambda itself, not enclosing scope - It treats parameters strictly, without implicit unpacking/optionality
Now, :+.to_proc
behaves this way:
PLUS = :+.to_proc
PLUS.call(1, 2)
# => 3
PLUS.call([1, 2])
# ArgumentError (wrong number of arguments (given 0, expected 1))
# Tried to call [1, 2].+(), not 1.+(2), so no unpacking
Whilst lambda would behave this way:
PLUS_L = lambda { |obj, *rest| obj.send(:+, *rest) }
PLUS_L.call(1, 2)
# => 3
PLUS_L.call([1, 2])
# ArgumentError (wrong number of arguments (given 0, expected 1))
# Explicit return:
lambda { |obj, *rest| return obj.send(:+, *rest) }.call(1, 2)
# => 3
....and proc will behave this way:
PLUS_P = lambda { |obj, *rest| obj.send(:+, *rest) }
PLUS_P.call(1, 2)
# => 3
PLUS_P.call([1, 2])
# => 3
# Implicit unpacking
# Explicit return:
proc { |obj, *rest| return obj.send(:+, *rest) }.call(1, 2)
# --- returns from the enclosing scope
So, :<sym>.to_proc
behaves exactly like lambda, and nothing like proc.
The only thing that differs from the equivalent lambda is...
PLUS.parameters # => [[:rest]]
PLUS_L.parameters # => [[:req, :obj], [:rest, :rest]]
(which is ideally to be fixed too, as in fact the first parameter is indeed mandatory.)
Can you please show me the case when :<sym>.to_proc
does NOT behave like lambda?..
Just curious: How do you want to use the result of
lambda?
?
@mame (Yusuke Endoh) For explanatory and educational purposes, at least. For example, in this article, I am showing some funny examples, and to explain why this works:
[1, 2, 3].zip([4, 4, 4]).map { |a, b| a + b }
...and this not:
[1, 2, 3].zip([4, 4, 4]).map(&:+)
...I'd like to just say "because :+.to_proc
is a lambda, as you can see", but what I really need to say is "becuase :+.to_proc
doesn't unpacks arguments, behaving like lambda... though it doesn't aknowledge it is"".
So, yep, debugging, explaining, teaching, this kind of things.
Updated by Eregon (Benoit Daloze) about 5 years ago
- Status changed from Rejected to Open
I agree with @zverok (Victor Shepelev) here, a method behaves as a lambda, and doesn't unpack arguments (except a few special methods that specifically do that).
@nobu (Nobuyoshi Nakada) I think we should merge your PR. Could you show an example of a Symbol#to_proc Proc that behaves like a proc and not a lambda? I think that's only rare exceptions (due to that method semantic, not due to the generated Proc), and so Symbol#to_proc should acknowledge it's a lambda.
Updated by mame (Yusuke Endoh) almost 5 years ago
- Tracker changed from Misc to Feature
- Assignee set to nobu (Nobuyoshi Nakada)
- Target version set to 36
At the previous meeting, matz said it should return true. Will do.
Updated by nobu (Nobuyoshi Nakada) almost 5 years ago
- Status changed from Open to Closed
Applied in changeset git|f0b815dc670b61eba1daaa67a8613ac431d32b16.
Proc
made by Symbol#to_proc
should be a lambda [Bug #16260]
Updated by hsbt (Hiroshi SHIBATA) about 4 years ago
- Target version changed from 36 to 3.0