Project

General

Profile

Actions

Feature #16260

closed

Symbol#to_proc behaves like lambda, but doesn't aknowledge it

Added by zverok (Victor Shepelev) over 4 years ago. Updated over 3 years ago.

Status:
Closed
Target version:
[ruby-core:95418]

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:

  1. Make it respond true to lambda? (and mention the semantics in docs)
  2. 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.

Actions #1

Updated by Eregon (Benoit Daloze) over 4 years ago

I think we should just return true for lambda?.

Proc has extra confusing behavior, e.g., #16166.

Updated by nobu (Nobuyoshi Nakada) over 4 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) over 4 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) over 4 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:

  1. Its return returns from lambda itself, not enclosing scope
  2. 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) over 4 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) over 4 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.

Actions #8

Updated by nobu (Nobuyoshi Nakada) about 4 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]

Actions #9

Updated by hsbt (Hiroshi SHIBATA) over 3 years ago

  • Target version changed from 36 to 3.0
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0