Ruby Forum Ruby-core > Proposal: Method#get_args

Posted by Yehuda Katz (wycats)
on 10.11.2008 00:18
(Received via mailing list)
I'd like to propose a way to introspect into the arguments of a method
object. Merb uses this feature, which is already available via ParseTree 
in
1.8.6, via compile-time in Rubinius, and via internal structures in 
JRuby.
I'd like to formalize the API so it can be added to 1.9, which cannot 
use
ParseTree.

The API would return a tuple representing the arguments. The first 
element
of the tuple is an array of the required and optional arguments. 
Required
arguments are a single-element tuple containing a symbol representing 
the
argument ([:foo]). Optional arguments with pure-literal defaults are
represented as a two-element tuple containing a symbol representing the
argument and the literal ([:foo, 1]). In the current implementations of 
this
API in 1.8, JRuby and Rubinius, non-literal defaults return nil. 
Instead, we
could create a constant that got returned (something like 
Method::OPTIONAL,
[:foo, OPTIONAL]).

Some examples:

def foo(bar); end
method(:foo).get_args #=> [[[:bar]], nil, nil]

def foo(bar, baz, bat = [], &blk); end
method(:foo).get_args #=> [[[:bar], [:baz], [:bat, []]], nil, :blk]

def foo(bar, *args); end
method(:foo).get_args #=> [[[:bar]], :args, nil]

def foo(bar, baz=1, *args, &blk); end
method(:foo).get_args #=> [[[:bar], [:baz, 1]], :args, :blk]

This could be handy for generating APIs for Ruby classes. In Merb, we 
use
this feature to convert inbound requests that list their parameters
(essentially) as a hash, into a Ruby method call. The benefit is the 
Merb
actions can be called from other actions as regular method calls.
Posted by Matt Todd (maraby)
on 10.11.2008 02:29
(Received via mailing list)
+1
(or is that frowned upon?)

I definitely think that this would be a great compliment to arity
inspection...

Matt
Posted by Austin Ziegler (austin)
on 10.11.2008 05:36
(Received via mailing list)
On Sun, Nov 9, 2008 at 5:46 PM, Yehuda Katz <wycats@gmail.com> wrote:
> I'd like to propose a way to introspect into the arguments of a method
> object. Merb uses this feature, which is already available via ParseTree in
> 1.8.6, via compile-time in Rubinius, and via internal structures in JRuby.
> I'd like to formalize the API so it can be added to 1.9, which cannot use
> ParseTree.

> The API would return a tuple representing the arguments.

Why a tuple, aside from being what's provided by the ParseTree API? I
like the idea, I'm just worried that a tuple may be a bit too opaque
for something that isn't called often. (We could make it two calls
that return a tuple vs. something more descriptive if performance is
an issue.) I also was reading not too long ago a section of David
Black's forthcoming "Well Grounded Rubyist" that tried to express the
different types of parameter lists available in Ruby 1.9 -- how does
this return value match up with what's actually possible?

-austin
Posted by Yukihiro Matsumoto (Guest)
on 10.11.2008 05:42
(Received via mailing list)
Hi,

In message "Re: [ruby-core:19759] Proposal: Method#get_args"
    on Mon, 10 Nov 2008 07:46:51 +0900, "Yehuda Katz" <wycats@gmail.com> 
writes:

|I'd like to propose a way to introspect into the arguments of a method
|object. Merb uses this feature, which is already available via ParseTree in
|1.8.6, via compile-time in Rubinius, and via internal structures in JRuby.
|I'd like to formalize the API so it can be added to 1.9, which cannot use
|ParseTree.
|
|The API would return a tuple representing the arguments. The first element
|of the tuple is an array of the required and optional arguments. Required
|arguments are a single-element tuple containing a symbol representing the
|argument ([:foo]). Optional arguments with pure-literal defaults are
|represented as a two-element tuple containing a symbol representing the
|argument and the literal ([:foo, 1]). In the current implementations of this
|API in 1.8, JRuby and Rubinius, non-literal defaults return nil. Instead, we
|could create a constant that got returned (something like Method::OPTIONAL,
|[:foo, OPTIONAL]).

As I said in person at the conference place, I am positive about the
idea.  But it's too late for 1.9.1.  I wish you proposed a few month
earlier.  We have to wait for 1.9.2, of which release date I don't
know (or settle) yet.

I think we have to address some issues:

* the name.  In Ruby, unlike Java, we rarely use the verb get for
  method names.  We prefer foo to get_foo, when foo can be considered
  as an attribute.  But in this case, args instead of get_args sounds
  bit weird to me.

* there is name-less rest argument (a, b, *), which just ignores the
  rest arguments.  I think we have give some meaningful value for that
  case too.

* 1.9 added mandatory argument at the bottom of argument list (after
  rest argument *, just before block argument &).  We have to address
  this in the returning tuple.

              matz.
Posted by Roger Pack (rogerdpack)
on 10.11.2008 07:40
(Received via mailing list)
> I'd like to propose a way to introspect into the arguments of a method
> object.

Some of this functionality is available currently via
RubyVM::InstructionSequence.disasm [1]
[I'm not sure if it includes which methods have named blocks or what
their name's are].
Also note that ruby_parser was recently updated to be 1.9 friendly.
You could use Method#source_location to lookup methods' source, parse
it and use that for introspection [albeit unreliably, and not for
dynamically generated methods, but it's an option].


[3] is a patch that creates InstructionSequence.disasm_proc [or could
override InstructionSequence.disasm to accept procs and methods],
which would...be about half-way there.  A more unified ruby-wide
approach would be sweet, though.


I think what we would be really sweet would be having a unified API
for being able to pull the parse tree out of methods and procs--then
people could rewrite them etc [currently possible in 1.8, not 1.9, as
noted previously ].

The reason I think it would be nice is that [as parse tree does
currently] the tuple output is something like
[:name, [:block, [:code_for_default_assignment]] which would be even
more useful.

Note also that I believe Paul Brannan's ruby internals allows access
to introspecting blocks, etc. [2] and that there have been requests in
the past for more argument introspection [4]

Thanks for reading :)
-=R

[1] http://blog.zenspider.com/2008/06/dear-lazyweb-proc-disassembly.html
[2] http://blog.zenspider.com/2008/06/dear-lazyweb-proc-disassembly.html
[3] http://redmine.ruby-lang.org/issues/show/578
[4] http://redmine.ruby-lang.org/issues/show/440
Posted by Meinrad Recheis (Guest)
on 10.11.2008 10:29
(Received via mailing list)
On Mon, Nov 10, 2008 at 5:32 AM, Austin Ziegler 
<halostatue@gmail.com>wrote:

>
> Why a tuple, aside from being what's provided by the ParseTree API? I
> like the idea, I'm just worried that a tuple may be a bit too opaque
> for something that isn't called often. (We could make it two calls
> that return a tuple vs. something more descriptive if performance is
> an issue.) I also was reading not too long ago a section of David
> Black's forthcoming "Well Grounded Rubyist" that tried to express the
> different types of parameter lists available in Ruby 1.9 -- how does
> this return value match up with what's actually possible?


One possibility here would be a class (ie. MethodArgument) modelling a
method parameter, and having the API return an array of MethodArguments

class MethodArgument
  def name
  def position
  def is_mandatory?
  def is_block?
  def is_rest_argument?
  def to_s <-- return a string that looks like ruby code (i.e. &block 
for a
block parameter that has been named "block")
end

-- henon
Posted by Austin Ziegler (austin)
on 10.11.2008 13:53
(Received via mailing list)
On Mon, Nov 10, 2008 at 4:26 AM, Meinrad Recheis
<meinrad.recheis@gmail.com> wrote:
>>
> method parameter, and having the API return an array of
> end
Yes. I think that would possibly help handle the case of the mandatory
argument after the greedy argument e.g., m(a, b, *c, d, &e) -- and by
having a name of nil, could handle what Matz mentioned about the
anonymous greedy argument, e.g., m(a, b, *). However, a class feels too
heavyweight for the metaprogramming case, while the anonymous tuple
feels a little too lightweight and devoid of context, at least to me.

-austin
Posted by Brian Candler (candlerb)
on 10.11.2008 14:57
(Received via mailing list)
On Mon, Nov 10, 2008 at 09:50:32PM +0900, Austin Ziegler wrote:
> However, a class feels too
> heavyweight for the metaprogramming case, while the anonymous tuple
> feels a little too lightweight and devoid of context, at least to me.

A Struct perhaps? You can always use to_a to get a tuple back.

(It is still a Class of course :-)
Posted by Dave Thomas (Guest)
on 10.11.2008 15:27
(Received via mailing list)
On Nov 10, 2008, at 7:54 AM, Brian Candler wrote:

>> However, a class feels too
>> heavyweight for the metaprogramming case, while the anonymous tuple
>> feels a little too lightweight and devoid of context, at least to me.
>
> A Struct perhaps? You can always use to_a to get a tuple back.

Or an array of strings?

   def fred(a, b=2, *c &block)
   end

   method(:fred).arguments => ["a", "b=2", "*c", "&block" ]
Posted by Austin Ziegler (austin)
on 10.11.2008 15:55
(Received via mailing list)
On Mon, Nov 10, 2008 at 9:24 AM, Dave Thomas <dave@pragprog.com> wrote:
>  method(:fred).arguments => ["a", "b=2", "*c", "&block" ]
That feels a little better from my ability to understand it, but then
programs which want to do metaprogramming have to do a lot of extra work
(split on /=/; know that "*" means greedy, etc.). I was sort of thinking
an array of Hashes, but that's not necessarily any lighter weight than a
Struct or a dedicated class.

  method(:fred).arguments =>
    [ { :name => :a },
      { :name => :b, :default => 2 },
      { :name => :c, :greedy => true },
      { :name => block, :block => true } ]

Playing with some other constructs:

  def wilma(a, *, c); end
  method(:wilma).arguments =>
    [ { :name => :a },
      { :name => nil, :greedy => true },
      { :name => :c } ]

  def barney(a, b, c = b); end
  method(:barney).arguments =>
    [ { :name => :a },
      { :name => :b },
      { :name => :c, :default => :b } ]

I'm not really happy with the last -- because it conflicts with:

  def betty(a, b, c = :b); end
  method(:betty).arguments =>
    [ { :name => :a },
      { :name => :b },
      { :name => :c, :default => :b } ]

Or would that be:

  method(:betty).arguments =>
    [ { :name => :a },
      { :name => :b },
      { :name => :c, :default => :":b" } ]

Musings right now on how to express certain things.

I think it could be useful to be able to get the symbolic information
for a default parameter (even if it's a method call).

-austin
Posted by Dave Thomas (Guest)
on 10.11.2008 16:05
(Received via mailing list)
On Nov 10, 2008, at 8:50 AM, Austin Ziegler wrote:

> That feels a little better from my ability to understand it, but then
> programs which want to do metaprogramming have to do a lot of extra  
> work
> (split on /=/; know that "*" means greedy, etc.). I was sort of  
> thinking
> an array of Hashes, but that's not necessarily any lighter weight  
> than a
> Struct or a dedicated class.

Actually, I'm not convinced there's any extra work compared to
handling a structured type coming back. I tried a few use cases, and
it seems that the conditional logic I'd need is the same in all cases,
and the string representation has simplicity on its side.


Dave
Posted by Paul Brannan (cout)
on 10.11.2008 16:21
(Received via mailing list)
On Mon, Nov 10, 2008 at 07:46:51AM +0900, Yehuda Katz wrote:
> method(:foo).get_args #=> [[[:bar], [:baz, 1]], :args, :blk]
I like this idea, but I would rather see an object returned rather than
an array.

Ruby-internal supports this via signature.rb:

http://github.com/cout/ruby-internal/tree/master/lib/internal/method/signature.rb
http://github.com/cout/ruby-internal/tree/master/lib/internal/proc/signature.rb

Though IMO the API is pretty messy (at the time it was better for me to
have a poorly designed API than none at all).

Paul
Posted by Hongli Lai (Guest)
on 11.11.2008 01:12
(Received via mailing list)
Yukihiro Matsumoto wrote:
> * the name.  In Ruby, unlike Java, we rarely use the verb get for
>   method names.  We prefer foo to get_foo, when foo can be considered
>   as an attribute.  But in this case, args instead of get_args sounds
>   bit weird to me.

How about calling it "parameters"? The argument is the value that gets
passed while the parameter is part of the method's declaration. From
http://en.wikipedia.org/wiki/Parameter_(computer_science):

   "A parameter represents a value that the procedure expects you to
supply when you call it. The procedure's declaration defines its 
parameters.
   ...
   An argument represents the value you supply to a procedure parameter
when you call the procedure."

The word "parameter" would make more sense than "argument" in this case.
Posted by Roger Pack (rogerdpack)
on 11.11.2008 01:22
(Received via mailing list)
The only question I have is why would one want to know the names of
the parameters, not just how many (since they're only used locally).
If we do want to know the names of parameters, then we might as well
want their default values [and if we have that...we might as well open
up access somehow to the code of the method/proc, as well, since we'll
have built a code outputter].
Just my $0.02.
-=R
Posted by Hongli Lai (Guest)
on 11.11.2008 01:45
(Received via mailing list)
Roger Pack wrote:
> The only question I have is why would one want to know the names of
> the parameters, not just how many (since they're only used locally).
> If we do want to know the names of parameters, then we might as well
> want their default values [and if we have that...we might as well open
> up access somehow to the code of the method/proc, as well, since we'll
> have built a code outputter].
> Just my $0.02.
> -=R

Merb uses it to map HTTP parameters to method parameters.
Posted by Thomas Sawyer (7rans)
on 11.11.2008 01:49
(Received via mailing list)
On Nov 10, 7:18 pm, "Roger Pack" <rogerpack2...@gmail.com> wrote:
> The only question I have is why would one want to know the names of
> the parameters, not just how many (since they're only used locally).
> If we do want to know the names of parameters, then we might as well
> want their default values [and if we have that...we might as well open
> up access somehow to the code of the method/proc, as well, since we'll
> have built a code outputter].
> Just my $0.02.

One could use it for documenting external interfaces. Eg. A command
line parser could map command line to method definition and provide
help information based on these parameter names.

Just a thought.
Posted by Roger Pack (rogerdpack)
on 11.11.2008 02:14
(Received via mailing list)
> One could use it for documenting external interfaces. Eg. A command
> line parser could map command line to method definition and provide
> help information based on these parameter names.
>
> Just a thought.

Thinking out loud, would also be nice to have access to the
'docstring' of a function [a la python], thus allowing dynamically
generated methods to have their own documentation lookup.

This and the arguments would be possible if you had access to the AST:)

Note also that the functionality merb wants is possibly available via
RubyVM::InstructionSequence.
Just $0.02 again though they're not worth that much.
Take care.
-=R
Posted by Austin Ziegler (austin)
on 11.11.2008 04:52
(Received via mailing list)
On Mon, Nov 10, 2008 at 8:11 PM, Roger Pack <rogerpack2005@gmail.com> 
wrote:
>> One could use it for documenting external interfaces. Eg. A command
>> line parser could map command line to method definition and provide
>> help information based on these parameter names.
>> Just a thought.
> Note also that the functionality merb wants is possibly available via
> RubyVM::InstructionSequence.

Is RubyVM::InstructionSequence considered portable?

-austin
Posted by Jeremy McAnally (Guest)
on 11.11.2008 05:02
(Received via mailing list)
ruby_parser gives you this with the #comments method.

As for the get_args/parameters method, I think that would be
fantastic.  This sort of thing would make something like a new RDoc/a
live RDoc embedded in an IDE much easier.

--Jeremy

On Mon, Nov 10, 2008 at 7:11 PM, Roger Pack <rogerpack2005@gmail.com> 
wrote:
> This and the arguments would be possible if you had access to the AST:)
>
> Note also that the functionality merb wants is possibly available via
> RubyVM::InstructionSequence.
> Just $0.02 again though they're not worth that much.
> Take care.
> -=R
>
>



--
http://jeremymcanally.com/
http://entp.com/
http://omgbloglol.com

My books:
http://manning.com/mcanally/
http://humblelittlerubybook.com/ (FREE!)
Posted by Roger Pack (rogerdpack)
on 11.11.2008 05:08
(Received via mailing list)
> Is RubyVM::InstructionSequence considered portable?

Works for me in Linux, win32, and OS X, so...I'd imagine wherever MRI
runs it will.  Just guessing.

-=R
Posted by Austin Ziegler (austin)
on 11.11.2008 05:18
(Received via mailing list)
On Mon, Nov 10, 2008 at 11:05 PM, Roger Pack <rogerpack2005@gmail.com> 
wrote:
>> Is RubyVM::InstructionSequence considered portable?
> Works for me in Linux, win32, and OS X, so...I'd imagine wherever MRI
> runs it will.  Just guessing.

What I meant is whether it's meant to be portable among interpreters.

I suspect that it isn't. I, for one, want to see as few libraries that
are doing something like:

  case RUBY_ENGINE
  when 'mri'
  when 'jruby'
  ...
  end

-austin
Posted by Roger Pack (rogerdpack)
on 11.11.2008 07:20
(Received via mailing list)
> I suspect that it isn't. I, for one, want to see as few libraries that
> are doing something like:
>
>  case RUBY_ENGINE
>  when 'mri'
>  when 'jruby'
>  ...
>  end

Oh I'm favor of it--I would however suggest that allowing for
"docstrings" and/or parse trees of methods would be even more powerful
overall :)

Stay well.
-=R
Posted by Yehuda Katz (wycats)
on 11.11.2008 08:06
(Received via mailing list)
Lots of responses here.
1) I'd be in favor of an object-based API. I'll probably post tomorrow 
with
some thoughts on that. I only provided the tuple-based API because 
already
have it in all impls (except 1.9) and it already works.

2) I agree that there are potentially more powerful things that we 
*could*
do. However, those things would take a lot of time to get right. This
feature is simple and limited enough to be definitely supportable across
implementations, and possible to nail down quickly and efficiently.

What I specifically would like to avoid is a very long discussion about
trying to get a perfect, large API to handle a superset of these 
features
(such as full support for a locked-down AST), when the simple version 
would
be extremely helpful all on its own.

Hope that made sense,

-- Yehuda
Posted by Roger Pack (rogerdpack)
on 11.11.2008 08:28
(Received via mailing list)
> Lots of responses here.
> 1) I'd be in favor of an object-based API. I'll probably post tomorrow with
> some thoughts on that. I only provided the tuple-based API because already
> have it in all impls (except 1.9) and it already works.

I agree that a simpler implementation (like one that didn't include
default values--just names) would probably be easier.

I wonder if you'd be able to create fairly easily an implementation
for 1.9.1 [note--not 1.9.2] using InstructionSequence.   I'd be happy
to help :)
Surprising there's not some gem out there that simplifies this process.
Thanks much.
-=R


>> class Go; def go3(a, b=2, &block); end; end
>> print RubyVM::InstructionSequence.disasm Go.allocate.method(:go3)
== disasm: <RubyVM::InstructionSequence:go3@(irb)>======================
local table (size: 4, argc: 1 [opts: 2, rest: -1, post: 0, block: 2] s0)
[ 4] a<Arg>     [ 3] b<Opt=0>   [ 2] block<Block>
0000 putobject        2                                               ( 
15)
......
Posted by Mikael Høilund (Guest)
on 11.11.2008 12:04
(Received via mailing list)
Allow me to throw in my ~.116892074 DKK;

I'm in favor of using a class for representing the parameter data.
It'll allow for methods like allows?(some, arguments), which will tell
you if calling the method with those arguments will throw an
ArgumentError or not. This might, however, be better suited to be in
the Method/UnboundMethod class.

It will also make handling of the returned object much prettier. I'd
much rather see

method(:foo).parameters.names.include? :some_param

than

method(:foo).parameters.any? { |param| param.include? :some_param if
param.kind_of? Array }

or however it would be represented. Similarly, what expresses what you
want the most; `parameters.takes_block?` or `!parameters.last.nil?`?

I'm also in favor of supplying the default value. In far the most
cases the default value is nil (in my experience), but not always.
This means I'll have to look up the method (don't get me started on if
the method is dynamically generated) just to pass an optional argument
that follows another optional argument I don't want to change.

Default values, however, I can see becoming a bit of a hassle. Unless
it's to be represented as a string of Ruby code, cases like `def foo
bar, baz = "default-#{bar}"` will be quite difficult to express, let
alone in a way that's easy and nice to handle.

Ultimately, I'd like to propose that the parameters method also be
added to the Proc class.
Posted by Paul Brannan (cout)
on 11.11.2008 15:22
(Received via mailing list)
On Tue, Nov 11, 2008 at 08:01:56PM +0900, Mikael H?ilund wrote:
> Default values, however, I can see becoming a bit of a hassle. Unless  
> it's to be represented as a string of Ruby code, cases like `def foo  
> bar, baz = "default-#{bar}"` will be quite difficult to express, let  
> alone in a way that's easy and nice to handle.

If we had unbound procs, it could be represented as an unbound proc.

(rebinding a proc to a different binding is inherently unsafe,
unfortunately)

Paul
Posted by Nobuyoshi Nakada (nobu)
on 11.11.2008 20:01
(Received via mailing list)
Hi,

At Mon, 10 Nov 2008 13:39:15 +0900,
Yukihiro Matsumoto wrote in [ruby-core:19769]:
> 
> * 1.9 added mandatory argument at the bottom of argument list (after
>   rest argument *, just before block argument &).  We have to address
>   this in the returning tuple.

* how to represent default arguments. They can be arbitrary
  expressions not only simple literals, for instance,
  preceeding parameters can be there, and any (parenthesized)
  statements e.g. if, case, while, class, and etc.

  And as 1.9 merges bytecodes of those expressions into the
  method body, so I don't think it's possible to discompile.
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:22
(Received via mailing list)
Dave Thomas wrote:
> a structured type coming back. I tried a few use cases, and it seems 
> that the conditional logic I'd need is the same in all cases, and the 
> string representation has simplicity on its side.

It's actually an array of arrays with a set structure; I tend to like
the array since it's a trivial bit of structured data and doesn't bloat
up the global namespace with yet another class with only a single
purpose. Since the reuse case for a MethodArg type would be extremely
limited, I vote for arrays.

Whatever it ends up as, we'll provide support in JRuby for both 1.8 and
1.9 modes. For anyone interested, the code (all ruby) to add this
feature in JRuby is below.

- Charlie

require 'java'
require 'jruby'

module GetArgs
   Methods = org.jruby.internal.runtime.methods

   def get_args
     real_method = JRuby.reference(self)

     # hack to expose a protected field; could be improved in 1.1.5
     method_field = 
org.jruby.RubyMethod.java_class.declared_field(:method)

     method_field.accessible = true

     dyn_method = method_field.value(real_method)

     case dyn_method
     when Methods.MethodArgs
       return build_args(dyn_method.args_node)
     else
       raise "Can't get args from method: #{self}"
     end
   end

   def build_args(args_node)
     args = []
     required = []
     optional = []

     # required args
     if (args_node.args && args_node.args.size > 0)
       required << args_node.args.child_nodes.map { |arg|
[arg.name.to_s.intern] }
     end

     # optional args
     if (args_node.opt_args && args_node.opt_args.size > 0)
       optional << args_node.opt_args.child_nodes.map do |arg|
         name = arg.name.to_s.intern
         value_node = arg.value_node
         case value_node
         when org.jruby.ast::FixnumNode
           value = value_node.value
         when org.jruby.ast::SymbolNode
           value = value_node.get_symbol(JRuby.runtime)
         when org.jruby.ast::StrNode
           value = value_node.value
         else
           value = nil
         end
         [name, value]
       end
     end

     first_args = required.first
     optional.first.each {|arg| first_args << arg} if optional.first

     args = [first_args]

     rest = args_node.rest_arg_node
     args << (rest ? rest.name.to_s.intern : nil)

     block = args_node.block_arg_node
     args << (block ? block.name.to_s.intern : nil)

     args
   end
end
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:24
(Received via mailing list)
Nobuyoshi Nakada wrote:
> * how to represent default arguments. They can be arbitrary
>   expressions not only simple literals, for instance,
>   preceeding parameters can be there, and any (parenthesized)
>   statements e.g. if, case, while, class, and etc.

For the JRuby args support (and I believe the ParseTree support) we
agreed only the simplest literal types would be supported, which
includes Fixnum, Symbol, and String in JRuby. This isn't intended to be
a complete parameter-list specification as much as informational, and
arbitrary defaults would fall outside the domain of informational.

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:30
(Received via mailing list)
Yukihiro Matsumoto wrote:
> I think we have to address some issues:
> 
> * the name.  In Ruby, unlike Java, we rarely use the verb get for
>   method names.  We prefer foo to get_foo, when foo can be considered
>   as an attribute.  But in this case, args instead of get_args sounds
>   bit weird to me.

Someone else suggested "parameters", which isn't too bad. "arguments"
also seems fine to me.

> * there is name-less rest argument (a, b, *), which just ignores the
>   rest arguments.  I think we have give some meaningful value for that
>   case too.

:* seems like a good identifier, since it's not valid as a local var
name. So we'd have this:

def foo(bar, *); end
method(:foo).get_args #=> [[[:bar]], :*, nil]

> * 1.9 added mandatory argument at the bottom of argument list (after
>   rest argument *, just before block argument &).  We have to address
>   this in the returning tuple.

This is perhaps where a more structured type starts to sound better; we
could expand the array-based structure, but then what happens with
future changes to supported args layout?

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:31
(Received via mailing list)
Roger Pack wrote:
> I think what we would be really sweet would be having a unified API
> for being able to pull the parse tree out of methods and procs--then
> people could rewrite them etc [currently possible in 1.8, not 1.9, as
> noted previously ].
> 
> The reason I think it would be nice is that [as parse tree does
> currently] the tuple output is something like
> [:name, [:block, [:code_for_default_assignment]] which would be even
> more useful.

The problem with presenting parse trees are manifold:

* The parse tree needs to be standardized or frozen
* The parse tree needs to be retrievable at runtime (meaning you have to
keep an AST or something similar even when you're not using it...huge
memory waste)
* Parse trees reflect internal details of implementation (like 1.8/1.9
parsing Iter as wrapping calls, and the reverse in JRuby).

I'd strongly prefer a few targeted reflective APIs, like get_args, be
added instead.

> Note also that I believe Paul Brannan's ruby internals allows access
> to introspecting blocks, etc. [2] and that there have been requests in
> the past for more argument introspection [4]

JRuby also provides a means of accessing all JRuby internals, which is
how this args feature was implemented entirely in Ruby. I recommend
similar features for other impls, but I imagine it's a bit harder to do
across the board.

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:33
(Received via mailing list)
Roger Pack wrote:
> Oh I'm favor of it--I would however suggest that allowing for
> "docstrings" and/or parse trees of methods would be even more powerful
> overall :)

Docstrings for sure. I've lobbied for such in the past, but never had
time to make a strong enough case :)

Exposed parse trees are a big fat can of worms. I'd agree to limited
expression trees being added in specific cases, though.

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:36
(Received via mailing list)
Jeremy McAnally wrote:
> ruby_parser gives you this with the #comments method.
> 
> As for the get_args/parameters method, I think that would be
> fantastic.  This sort of thing would make something like a new RDoc/a
> live RDoc embedded in an IDE much easier.

FWIW, I fully support parse tree-based tooling that is *offline*, i.e.
parses content immediately before introspecting it, rather than
ParseTree's exposure of internal AST structure at runtime. I'd love to
see ruby_parser used heavily for this kind of offline
preprocessing/analysis, since it's both portable and much more 
future-proof.

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:40
(Received via mailing list)
Mikael Høilund wrote:
> or however it would be represented. Similarly, what expresses what you 
> want the most; `parameters.takes_block?` or `!parameters.last.nil?`?

Strongest argument I've seen for a real class. I'm more in favor of that
now.

> I'm also in favor of supplying the default value. In far the most cases 
> the default value is nil (in my experience), but not always. This means 
> I'll have to look up the method (don't get me started on if the method 
> is dynamically generated) just to pass an optional argument that follows 
> another optional argument I don't want to change.

Oh yeah, nil...I knew I missed something in the JRuby impl.

I suppose a short list of defaults that should work would be:

* literal numerics
* symbols
* strings
* nil
* booleans
* empty array or hash

It could also potentially support more, though these start to get a
little rare and subjective:

* regexp
* hash or array with all literals (nested?)
* constants

Again...I'm in favor of just simple literals, excluding empty array and
hash.

> Default values, however, I can see becoming a bit of a hassle. Unless 
> it's to be represented as a string of Ruby code, cases like `def foo 
> bar, baz = "default-#{bar}"` will be quite difficult to express, let 
> alone in a way that's easy and nice to handle.

A string of Ruby code is next to impossible to do efficiently and
portably, so I think it can be the above set of literals at best.

- Charlie
Posted by Dave Thomas (Guest)
on 11.11.2008 20:51
(Received via mailing list)
On Nov 11, 2008, at 1:37 PM, Charles Oliver Nutter wrote:

> Strongest argument I've seen for a real class. I'm more in favor of  
> that now.

Before this all grows to some large API, I was wondering: what actual
value are we trying to generate here. "Reflecting on method
parameters" is a technique, but what do we need to do with the result
that needs all this complexity? (I'm not saying it isn't needed, I'm
just having a hard time seeing what cold be needed that isn't answered
with an array of strings.



Dave
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 20:51
(Received via mailing list)
Paul Brannan wrote:
> On Tue, Nov 11, 2008 at 08:01:56PM +0900, Mikael H?ilund wrote:
>> Default values, however, I can see becoming a bit of a hassle. Unless  
>> it's to be represented as a string of Ruby code, cases like `def foo  
>> bar, baz = "default-#{bar}"` will be quite difficult to express, let  
>> alone in a way that's easy and nice to handle.
> 
> If we had unbound procs, it could be represented as an unbound proc.
> 
> (rebinding a proc to a different binding is inherently unsafe,
> unfortunately)

Yeah, I don't even know how that would work; block bodies are parsed
based on their surrounding scopes.

- Charlie
Posted by Paul Brannan (cout)
on 11.11.2008 21:16
(Received via mailing list)
On Wed, Nov 12, 2008 at 03:58:48AM +0900, Nobuyoshi Nakada wrote:
> * how to represent default arguments. They can be arbitrary
>   expressions not only simple literals, for instance,
>   preceeding parameters can be there, and any (parenthesized)
>   statements e.g. if, case, while, class, and etc.

Control structures are tricky but not impossible to decompile.  For 
example,
it's a reasonable to assume that a branchif instruction that branches
backward in the sequence is probably a while loop.

>   And as 1.9 merges bytecodes of those expressions into the
>   method body, so I don't think it's possible to discompile.

The instructions are in the method body, but are pointed to by the
arg_opt_table.  Each default value expression can be calculated by
decompiling the corresponding instructions in the table.

Paul
Posted by Paul Brannan (cout)
on 11.11.2008 21:20
(Received via mailing list)
On Wed, Nov 12, 2008 at 04:28:32AM +0900, Charles Oliver Nutter wrote:
> The problem with presenting parse trees are manifold:
>
> * The parse tree needs to be standardized or frozen

I disagree.  The parse tree can be presented to the user as an
implementation- and version-specific interface.  There need not be any
guarantee that the tree or the interface to the tree be stable across
versions.

> * The parse tree needs to be retrievable at runtime (meaning you have to  
> keep an AST or something similar even when you're not using it...huge  
> memory waste)

This is a problem.  1.9 used to keep the AST around but doesn't anymore,
presumably for this reason.

> * Parse trees reflect internal details of implementation (like 1.8/1.9  
> parsing Iter as wrapping calls, and the reverse in JRuby).
>
> I'd strongly prefer a few targeted reflective APIs, like get_args, be  
> added instead.

I agree.  I don't want to have to walk a parse tree.

(but it's nice to be able to)

Paul
Posted by Yehuda Katz (wycats)
on 11.11.2008 21:42
(Received via mailing list)
Sent from my iPhone

On Nov 11, 2008, at 3:17 PM, Paul Brannan <pbrannan@atdesk.com> wrote:

> On Wed, Nov 12, 2008 at 04:28:32AM +0900, Charles Oliver Nutter wrote:
>> The problem with presenting parse trees are manifold:
>>
>> * The parse tree needs to be standardized or frozen
>
> I disagree.  The parse tree can be presented to the user as an
> implementation- and version-specific interface.  There need not be any
> guarantee that the tree or the interface to the tree be stable across
> versions.

Then it's not particularly useful, from a framework perspective.
Posted by Charles Oliver Nutter (Guest)
on 11.11.2008 21:46
(Received via mailing list)
Paul Brannan wrote:
> On Wed, Nov 12, 2008 at 04:28:32AM +0900, Charles Oliver Nutter wrote:
>> The problem with presenting parse trees are manifold:
>>
>> * The parse tree needs to be standardized or frozen
> 
> I disagree.  The parse tree can be presented to the user as an
> implementation- and version-specific interface.  There need not be any
> guarantee that the tree or the interface to the tree be stable across
> versions.

Doesn't matter if there's a guarantee. People will write to the MRI or
1.9 tree and never bother with others. I'm not sure I see the value of
adding such a core API that's not ever going to be compatible with other
impls, especially when a targeted approach would probably work fine for
most cases.

Exposing the raw parse tree is a cop-out, and doomed not only to
eventually fail (or require constant maintenance and version-specific
patching) across impls but across versions of the same impl.

>> * The parse tree needs to be retrievable at runtime (meaning you have to  
>> keep an AST or something similar even when you're not using it...huge  
>> memory waste)
> 
> This is a problem.  1.9 used to keep the AST around but doesn't anymore,
> presumably for this reason.

JRuby has it sometimes and doesn't have it other times, depending on
compilation settings. I don't want to keep it around if I don't have to,
since it represents a substantial memory hit.

- Charlie
Posted by Brian Candler (candlerb)
on 12.11.2008 10:06
(Received via mailing list)
On Wed, Nov 12, 2008 at 04:48:03AM +0900, Dave Thomas wrote:
> Before this all grows to some large API, I was wondering: what actual  
> value are we trying to generate here. "Reflecting on method parameters" 
> is a technique, but what do we need to do with the result that needs all 
> this complexity? (I'm not saying it isn't needed, I'm just having a hard 
> time seeing what cold be needed that isn't answered with an array of 
> strings.

I think we actually just want:

1. the names of the arguments
2. whether each one is mandatory, optional, or splat

I think that's all that's needed for method calls.

However I'm not sure what should happen for methods defined from blocks,
e.g. define_method(:foo) { |a,(b,c)| ... }
Posted by Brian Candler (candlerb)
on 12.11.2008 10:20
(Received via mailing list)
On Wed, Nov 12, 2008 at 06:01:40PM +0900, Brian Candler wrote:
> However I'm not sure what should happen for methods defined from blocks,
> e.g. define_method(:foo) { |a,(b,c)| ... }

Hmm.

  class Foo
    define_method(:foo) { |a,(b,c)| p a,b,c }
  end

  Foo.new.foo(1,[2,3])
  puts "Arity: #{Foo.new.method(:foo).arity}"

So foo has an arity of 2. But if you pass anything other than a 
two-element
array for the second argument, you get an ArgumentError: wrong number of
arguments.

This suggests to me that the arguments structure needs to be nestable; 
the
second argument is itself an argument list.
Posted by Paul Brannan (cout)
on 12.11.2008 14:29
(Received via mailing list)
On Wed, Nov 12, 2008 at 06:01:40PM +0900, Brian Candler wrote:
> I think we actually just want:
> 
> 1. the names of the arguments
> 2. whether each one is mandatory, optional, or splat

Or a rest or block argument.

(Or, in the future, a keyword or keyword rest argument)

Paul
Posted by Thomas Enebo (Guest)
on 12.11.2008 15:16
(Received via mailing list)
Brian Candler wrote:
>   end
>
>   
I question the validity of Arity for this case since the arity is really 
3.

The nested nature will definitely be needed:

  class Foo
    define_method(:foo) { |a,(b,(c))| p a,b,c }
  end

-Tom
Posted by Brian Candler (candlerb)
on 12.11.2008 17:53
(Received via mailing list)
On Wed, Nov 12, 2008 at 11:13:18PM +0900, Thomas Enebo wrote:
>>
>> This suggests to me that the arguments structure needs to be nestable; the
>> second argument is itself an argument list.
>>
>>   
> I question the validity of Arity for this case since the arity is really 3.

I don't think so:

class Foo
  define_method(:foo) { |a,(b,c)| p a,b,c }
end

Foo.new.foo(1,2,3) rescue(puts "pants")      # => pants
Foo.new.foo(1,[2]) rescue(puts "pants")      # => pants
Foo.new.foo(1,[2,3,4]) rescue(puts "pants")  # => pants

You must pass exactly two arguments to #foo, which by my understanding 
of
arity means the arity is 2. But the second argument must be an Array 
with
exactly two elements.

Regards,

Brian.
Posted by Charles Oliver Nutter (Guest)
on 12.11.2008 17:59
(Received via mailing list)
Brian Candler wrote:
> You must pass exactly two arguments to #foo, which by my understanding of
> arity means the arity is 2. But the second argument must be an Array with
> exactly two elements.

I think arity is nearly meaningless when argument lists perform
masgn-like array spreading.

I also think we may be overthinking the args thing a bit; the use case
Yehuda illustrated would only include the straightforward args cases.

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 12.11.2008 18:10
(Received via mailing list)
Paul Brannan wrote:
> On Wed, Nov 12, 2008 at 06:01:40PM +0900, Brian Candler wrote:
>> I think we actually just want:
>>
>> 1. the names of the arguments
>> 2. whether each one is mandatory, optional, or splat
> 
> Or a rest or block argument.
> 
> (Or, in the future, a keyword or keyword rest argument)

I'm leaning toward a wholly array-based structure that specifies all
this but is future-proof... a couple examples

def foo(a, b = 1, *c, d)
=> [[:req, :a], [:opt, :b, 1], [:rest, :c], [:req, :d]]

def foo(a, (b, c))
=> [[:req, :a], [[:req, :a], [:req, :b]]]

And hypothetical keyword args, identified like symbols...

def foo(a, :b, :c)
=> [[:req, :a], [:key, :b], [:key, :c]]

I think just about all combinations of args can be specified this way,
and since the structure is really fluid I'm not sure any concrete class
could represent all possible structures.

This also leans back toward Yehuda's original goal of a simple
array-based structure without sacrificing the ability to express all
combinations of args now and into the future.

- Charlie
Posted by Austin Ziegler (austin)
on 12.11.2008 18:28
(Received via mailing list)
On Wed, Nov 12, 2008 at 12:06 PM, Charles Oliver Nutter
<charles.nutter@sun.com> wrote:
>
> def foo(a, :b, :c)
> => [[:req, :a], [:key, :b], [:key, :c]]
>
> I think just about all combinations of args can be specified this way,
> and since the structure is really fluid I'm not sure any concrete
> class could represent all possible structures.
>
> This also leans back toward Yehuda's original goal of a simple
> array-based structure without sacrificing the ability to express all
> combinations of args now and into the future.

Going back to the examples I had before:

  foo(a, b = 2, *c, &block)
  => [[:req, :a], [:req, :b, 2], [:rest, :c], [:block, :block]]

  foo(a, *, c)
  => [[:req, :a], [:rest, nil], [:req, :c]]

  foo(a, b, c = b)
  => [[:req, a], [:req, b], [:opt, :c, :expr]]

I can see that, perhaps -- although I don't see what it gives us over an
array of hash values (except perhaps being lighter weight because of the
lack of names in the listed items).

-austin
Posted by Yehuda Katz (wycats)
on 12.11.2008 18:40
(Received via mailing list)
I am strongly in favor of this proposal. Getting something simple that
works and is futureproof would be fantastic.

Matz, does this satisfy your initial concerns?

Sent from my iPhone

On Nov 12, 2008, at 12:06 PM, Charles Oliver Nutter 
<charles.nutter@sun.com
Posted by Thomas Enebo (Guest)
on 12.11.2008 18:56
(Received via mailing list)
Brian Candler wrote:
>>> array for the second argument, you get an ArgumentError: wrong number of
> I don't think so:
> arity means the arity is 2. But the second argument must be an Array with
> exactly two elements.
>   
Yes I guess I can accept arity 2...though you still need three arguments
total to be supplied for the method to invoke (1 + 2 args in array).  I
guess arity really is not so helpful once you enter this type of 
signature.

-Tom
Posted by Charles Oliver Nutter (Guest)
on 12.11.2008 19:17
(Received via mailing list)
Austin Ziegler wrote:
> I can see that, perhaps -- although I don't see what it gives us over an
> array of hash values (except perhaps being lighter weight because of the
> lack of names in the listed items).

Representationally I'd say it's a wash...using hashes would not really
add anything nor subtract anything as far as I can say. In memory,
however, hashes are significantly more costly than a small array. I'd be
interested to see your idea of a hashified version, but I think the pure
array approach is going to be the lightest way by far.

- Charlie
Posted by Paul Brannan (cout)
on 12.11.2008 19:41
(Received via mailing list)
On Thu, Nov 13, 2008 at 02:06:15AM +0900, Charles Oliver Nutter wrote:
> def foo(a, b = 1, *c, d)
> => [[:req, :a], [:opt, :b, 1], [:rest, :c], [:req, :d]]

As an object, I would represent this as:

  #<Parameters params=[
    #<Parameter required=true, name=:a>,
    #<Parameter required=false, default="1", name=:b>,
    #<Parameter required=false, rest=true, name=:c>,
    #<Parameter required=true, rest=true, name=:d>
  ]>

> def foo(a, (b, c))
> => [[:req, :a], [[:req, :a], [:req, :b]]]

And this as:

  #<Parameters params=[
    #<Parameter required=true, name=:a>,
    #<Parameters params=[
      #<Parameter required=true name=:a>,
      #<Parameter required=true name=:b>
    ]
  ]>

Paul
Posted by Charles Oliver Nutter (Guest)
on 12.11.2008 19:50
(Received via mailing list)
Paul Brannan wrote:
> As an object, I would represent this as:
...
> And this as:
...

At that point I think we gain very little over arrays of arrays, since
we have to walk Parameters of Parameter(s) in almost exactly the same 
way...

- Charlie
Posted by Paul Brannan (cout)
on 12.11.2008 20:06
(Received via mailing list)
On Thu, Nov 13, 2008 at 03:46:58AM +0900, Charles Oliver Nutter wrote:
> At that point I think we gain very little over arrays of arrays, since  
> we have to walk Parameters of Parameter(s) in almost exactly the same 
> way...

That depends on what what you want to do with the structure.

In:

  class Foo
    define_method(:foo) { |a, (b, c), d| p a, b, c, d }
  end

I can still find out whether the 3rd parameter (d) is required with:

  params = Foo.method(:foo).parameters
  p params[2].required?

vs:

  p params[2][0] == :req

or I can find out if it is optional with:

  p params[2].optional?

vs:

  p params[2][0] != :req

Paul
Posted by Jim Weirich (Guest)
on 12.11.2008 20:26
(Received via mailing list)
On Nov 12, 2008, at 1:46 PM, Charles Oliver Nutter wrote:

> At that point I think we gain very little over arrays of arrays,  
> since we have to walk Parameters of Parameter(s) in almost exactly  
> the same way...

Not much difference in the walking, but:

(1) I can ask parameter.required?  rather than parameter.first
== :req.  Seems a win to me.

(2) You could put a special to_s method on Parameters that formats the
parameter list intelligently.  (e.g. parameters.to_s => "(a, b = 1,
*c, d)" )
Posted by Jim Deville (Guest)
on 12.11.2008 20:36
(Received via mailing list)
> > since we have to walk Parameters of Parameter(s) in almost exactly
>
> --
> -- Jim Weirich
> -- jim.weirich@gmail.com
>
>

It could also behave like an array with Enumerable, each and :[] 
defined. It should still be light weight, just cleaner to query.
Posted by Brian Candler (candlerb)
on 12.11.2008 20:43
(Received via mailing list)
On Thu, Nov 13, 2008 at 04:33:07AM +0900, Jim Deville wrote:
> It could also behave like an array with Enumerable, each and :[] defined. It should still be light weight, just cleaner to query.

I can see the reasoning behind having a Parameter class (although I 
still
think a two-tuple would be simpler)

I can't see, however, why you wouldn't just have an Array of Parameter
objects, rather than a separate Parameters container object.
Posted by Jim Weirich (Guest)
on 12.11.2008 21:24
(Received via mailing list)
On Nov 12, 2008, at 2:40 PM, Brian Candler wrote:

> I can't see, however, why you wouldn't just have an Array of Parameter
> objects, rather than a separate Parameters container object.


Not sure you could do #2 (convenient :to_s method) in that case.
Whether #2 is worth the extra class is another question (and worth
asking)
Posted by Charles Oliver Nutter (Guest)
on 12.11.2008 22:16
(Received via mailing list)
Jim Weirich wrote:
> 
> On Nov 12, 2008, at 2:40 PM, Brian Candler wrote:
> 
>> I can't see, however, why you wouldn't just have an Array of Parameter
>> objects, rather than a separate Parameters container object.
> 
> 
> Not sure you could do #2 (convenient :to_s method) in that case.  
> Whether #2 is worth the extra class is another question (and worth asking)

I think I'm going to stick to my guns. I have full respect for the
Parameter class and the behavior it brings to the table. However:

* I can't see the value in introducing a new class *just* for this one
case. It's unlikely to see utility anywhere else, and this case doesn't
seem enough to warrant adding it.
* The parameter elements are structurally so simple there's barely any
behavior to add:

[param_type, param_name, default_value]

* Creating a type now damages the ability of this API to be stay
backward-compatible without having to add new methods as new argument
types come along. Do we define "keyword?" now? Do we provide support for
multiple block arguments now?

Since we're building up a very simple structure for informational
purposes only, the array of array seems like the least invasive and most
flexible to me. And adding new types for small globs of structured data
doesn't really seem like the Ruby way either:

* caller returns an array of strings representing the backtrace, not an
array of StackTraceElement. Exception#backtrace does the same.
* Kernel#local_variables returns an array of strings, not an array of
LocalVariableDescriptor elements.
* Kernel#select returns an array of arrays of IO, not an array of
SelectedIOCollection entries.

Let's try to avoid over-designing this. The convenience of having a few
nicer query or inspection methods is not enough to warrant saddling the
parameter-list elements with whole classes, IMHO.

(Also, for the record, I think this kind of bikeshedding is *great*.
These are important decisions, even if the details seem niggling or 
absurd.)

- Charlie
Posted by Austin Ziegler (austin)
on 12.11.2008 22:30
(Received via mailing list)
On Wed, Nov 12, 2008 at 1:13 PM, Charles Oliver Nutter
<charles.nutter@sun.com> wrote:
> Austin Ziegler wrote:
>> I can see that, perhaps -- although I don't see what it gives us over an
>> array of hash values (except perhaps being lighter weight because of the
>> lack of names in the listed items).
> Representationally I'd say it's a wash...using hashes would not really add
> anything nor subtract anything as far as I can say. In memory, however,
> hashes are significantly more costly than a small array. I'd be interested
> to see your idea of a hashified version, but I think the pure array approach
> is going to be the lightest way by far.

[ruby-core:19779]

-austin
Posted by Charles Oliver Nutter (Guest)
on 12.11.2008 22:39
(Received via mailing list)
Austin Ziegler wrote:
>> is going to be the lightest way by far.
> 
> [ruby-core:19779]

Righto...yep, I see where you're coming from, but I don't think it gains
much other than the ability to look up by name rather than by position.
And it's certain to be a lot heavier than just arrays, though the
"weight" of a structure that's hopefully used only rarely probably isn't
a huge deal.

- Charlie
Posted by Jim Weirich (Guest)
on 12.11.2008 23:05
(Received via mailing list)
On Nov 12, 2008, at 4:12 PM, Charles Oliver Nutter wrote:
> I think I'm going to stick to my guns. I have full respect for the  
> Parameter class and the behavior it brings to the table. However:
[...]
> * The parameter elements are structurally so simple there's barely  
> any behavior to add:
>
> [param_type, param_name, default_value]

<nod> And I'm not too happy with the required?/optional? suggested
behavior.  But I can't quite put my finger on why.

> * Creating a type now damages the ability of this API to be stay  
> backward-compatible without having to add new methods as new  
> argument types come along. Do we define "keyword?" now? Do we  
> provide support for multiple block arguments now?

Add new query methods or add new type symbols ... both seem equally
non-backward compatible.

> [...] And adding new types for small globs of structured data  
> doesn't really seem like the Ruby way either:
>
> * caller returns an array of strings representing the backtrace, not  
> an array of StackTraceElement. Exception#backtrace does the same.
> * Kernel#local_variables returns an array of strings, not an array  
> of LocalVariableDescriptor elements.
> * Kernel#select returns an array of arrays of IO, not an array of  
> SelectedIOCollection entries.

And while I strongly disagree with the design decisions on caller/
backtrace (there is a lot of useful information in the strings that
can only be retrieved via brittle parsing), the args arrays proposed
here don't require parsing so are more in line with the other two
suggestions.

> (Also, for the record, I think this kind of bikeshedding is *great*.  
> These are important decisions, even if the details seem niggling or  
> absurd.)

Agreed.

I see both sides, but I think I still lean slightly toward a
lightweight Parameter class.  Returning things ordered positionally[1]
in an array seems more like the FORTRAN way than the Ruby way.

--
-- Jim Weirich
-- jim.weirich@gmail.com

[1] I tend to prefer connascence of name over connascence of position.
Posted by Austin Ziegler (austin)
on 12.11.2008 23:51
(Received via mailing list)
On Wed, Nov 12, 2008 at 5:02 PM, Jim Weirich <jim.weirich@gmail.com> 
wrote:
> I see both sides, but I think I still lean slightly toward a lightweight
> Parameter class.  Returning things ordered positionally[1] in an array seems
> more like the FORTRAN way than the Ruby way.

I reiterate my suggestion to return a positional array of hashes. ;)

I think that the array is too brittle (it's not self-describing) and the
class is too heavyweight. The array solution suffers from an issue where
new descriptive elements have to be added to the end or you break older
programs. The only thing that seems to be better about a class rather
than a hash is that the class will describe the range of possibilities,
whereas the hash will just return what is.

-austin
Posted by Daniel Luz (Guest)
on 13.11.2008 06:57
(Received via mailing list)
Don't you think we're overthinking this all?

I'd personally just declare array-spreading parameters as anonymous
and non-optional (which is compatible with how arity works). Nesting
declarations might introduce a lot of complexity not only on the
implementation of this feature, but also on its use, especially for
arrays.

def foo(a, (b, c))
=> [[:req, :a], [:req, nil]]

Nameless parameters would be already possible due to "*", anyway.

Just my two cents.

--
Daniel Luz
Posted by Brian Candler (candlerb)
on 13.11.2008 10:50
(Received via mailing list)
On Thu, Nov 13, 2008 at 07:02:25AM +0900, Jim Weirich wrote:
> And while I strongly disagree with the design decisions on caller/ 
> backtrace (there is a lot of useful information in the strings that can 
> only be retrieved via brittle parsing), the args arrays proposed here 
> don't require parsing so are more in line with the other two  
> suggestions.

That does give me an idea. Since "?" is not legal in a variable name, 
then a
very simple option could be

   def foo(a, b, c=123, d=nil, *e, &blk)

     => ["a", "b", "?c", "?d", "*e", "&blk"]

   define_method(:bar) { |a,(b,c)|    }

     => ["a", ["b", "c"]]

The tuple alternative might be slightly easier to manipulate:

   [[:a,:req],[:b,:req],[:c,:opt],[:d,:opt],[:e,:rest],[:blk,:block]]

   [[:a,:req],[[[:b,:req],[:c,:req]],:group]]

but I don't think there's much in it.

(And I do realise that ?c has a different meaning in-line, but it's 
easier
to parse if the argument type discriminator is always the first 
character)
Posted by Brian Candler (candlerb)
on 13.11.2008 10:51
(Received via mailing list)
On Thu, Nov 13, 2008 at 06:27:17AM +0900, Austin Ziegler wrote:
> [ruby-core:19779]

The main problem with this, to me, is that it's a Perl object :-)
Posted by Thomas Sawyer (7rans)
on 13.11.2008 13:44
(Received via mailing list)
On Nov 13, 4:48 am, Brian Candler <B.Cand...@pobox.com> wrote:
> On Thu, Nov 13, 2008 at 06:27:17AM +0900, Austin Ziegler wrote:
> > [ruby-core:19779]
>
> The main problem with this, to me, is that it's a Perl object :-)

Don't fear the OOP.
Posted by Nobuyoshi Nakada (nobu)
on 14.11.2008 07:13
(Received via mailing list)
Hi,

At Thu, 13 Nov 2008 18:44:53 +0900,
Brian Candler wrote in [ruby-core:19915]:
> That does give me an idea. Since "?" is not legal in a variable name, then a
> very simple option could be
> 
>    def foo(a, b, c=123, d=nil, *e, &blk)
> 
>      => ["a", "b", "?c", "?d", "*e", "&blk"]

It's simplest.

>    define_method(:bar) { |a,(b,c)|    }
> 
>      => ["a", ["b", "c"]]

Is it really necessary?


Index: iseq.c
===================================================================
--- iseq.c  (revision 20232)
+++ iseq.c  (working copy)
@@ -1275,4 +1275,44 @@ rb_iseq_clone(VALUE iseqval, VALUE newcb
 }

+VALUE
+rb_iseq_parameters(const rb_iseq_t *iseq)
+{
+    int i, r;
+    VALUE a, n, args = rb_ary_new2(iseq->arg_size);
+
+    for (i = 0; i < iseq->argc; i++) {
+  a = rb_id2str(iseq->local_table[i]);
+  rb_ary_push(args, a ? a : Qnil);
+    }
+    r = iseq->arg_rest != -1 ? iseq->arg_rest :
+  iseq->arg_post_len > 0 ? iseq->arg_post_start :
+  iseq->arg_block != -1 ? iseq->arg_block :
+  iseq->arg_size;
+    for (; i < r; i++) {
+  a = rb_id2str(iseq->local_table[i]);
+  n = rb_str_new2("?");
+  if (a) rb_str_append(n, a);
+  rb_ary_push(args, n);
+    }
+    if (iseq->arg_rest != -1) {
+  a = rb_id2str(iseq->local_table[iseq->arg_rest]);
+  n = rb_str_new2("*");
+  if (a) rb_str_append(n, a);
+  rb_ary_push(args, n);
+    }
+    r = iseq->arg_post_start + iseq->arg_post_len;
+    for (i = iseq->arg_post_start; i < r; i++) {
+  a = rb_id2str(iseq->local_table[i]);
+  rb_ary_push(args, a ? a : Qnil);
+    }
+    if (iseq->arg_block != -1) {
+  a = rb_id2str(iseq->local_table[iseq->arg_block]);
+  n = rb_str_new2("&");
+  if (a) rb_str_append(n, a);
+  rb_ary_push(args, n);
+    }
+    return args;
+}
+
 /* ruby2cext */

Index: proc.c
===================================================================
--- proc.c  (revision 20232)
+++ proc.c  (working copy)
@@ -26,7 +26,10 @@ VALUE rb_cBinding;
 VALUE rb_cProc;

+VALUE rb_iseq_parameters(const rb_iseq_t *iseq);
+
 static VALUE bmcall(VALUE, VALUE);
 static int method_arity(VALUE);
 static VALUE rb_obj_is_method(VALUE m);
+static rb_iseq_t *get_method_iseq(VALUE method);

 /* Proc */
@@ -616,6 +619,12 @@ get_proc_iseq(VALUE self)
     GetProcPtr(self, proc);
     iseq = proc->block.iseq;
-    if (!RUBY_VM_NORMAL_ISEQ_P(iseq))
-  return 0;
+    if (!RUBY_VM_NORMAL_ISEQ_P(iseq)) {
+  NODE *node = (NODE *)iseq;
+  iseq = 0;
+  if (nd_type(node) == NODE_IFUNC && node->nd_cfnc == bmcall) {
+      /* method(:foo).to_proc */
+      iseq = get_method_iseq(node->nd_tval);
+  }
+    }
     return iseq;
 }
@@ -651,4 +660,34 @@ rb_proc_location(VALUE self)
 }

+static VALUE
+unnamed_parameters(int arity)
+{
+    VALUE param = rb_ary_new2((arity < 0) ? -arity : arity);
+    if (arity < 0) {
+  rb_ary_store(param, ~arity, rb_str_new2("*"));
+    }
+    else {
+  rb_ary_store(param, arity-1, Qnil);
+    }
+    return param;
+}
+
+/*
+ * call-seq:
+ *    proc.parameters  => array
+ *
+ * returns the parameter information of this proc
+ */
+
+static VALUE
+rb_proc_parameters(VALUE self)
+{
+    rb_iseq_t *iseq = get_proc_iseq(self);
+    if (!iseq) {
+  return unnamed_parameters(proc_arity(self));
+    }
+    return rb_iseq_parameters(iseq);
+}
+
 /*
  * call-seq:
@@ -1461,4 +1500,6 @@ get_method_iseq(VALUE method)
     body = data->body;
     switch (nd_type(body)) {
+      case NODE_BMETHOD:
+  return get_proc_iseq(body->nd_cval);
       case RUBY_VM_METHOD_NODE:
   GetISeqPtr((VALUE)body->nd_body, iseq);
@@ -1485,4 +1526,22 @@ rb_method_location(VALUE method)

 /*
+ * call-seq:
+ *    meth.parameters  => array
+ *
+ * returns the parameter information of this method
+ * or nil if this method was not defined in ruby (i.e. native)
+ */
+
+static VALUE
+rb_method_parameters(VALUE method)
+{
+    rb_iseq_t *iseq = get_method_iseq(method);
+    if (!iseq) {
+  return unnamed_parameters(method_arity(method));
+    }
+    return rb_iseq_parameters(iseq);
+}
+
+/*
  *  call-seq:
  *   meth.to_s      =>  string
@@ -1812,4 +1871,5 @@ Init_Proc(void)
     rb_define_method(rb_cProc, "curry", proc_curry, -1);
     rb_define_method(rb_cProc, "source_location", rb_proc_location, 0);
+    rb_define_method(rb_cProc, "parameters", rb_proc_parameters, 0);

     /* Exceptions */
@@ -1847,4 +1907,5 @@ Init_Proc(void)
     rb_define_method(rb_cMethod, "unbind", method_unbind, 0);
     rb_define_method(rb_cMethod, "source_location", rb_method_location, 
0);
+    rb_define_method(rb_cMethod, "parameters", rb_method_parameters, 
0);
     rb_define_method(rb_mKernel, "method", rb_obj_method, 1);
     rb_define_method(rb_mKernel, "public_method", rb_obj_public_method, 
1);
@@ -1865,4 +1926,5 @@ Init_Proc(void)
     rb_define_method(rb_cUnboundMethod, "bind", umethod_bind, 1);
     rb_define_method(rb_cUnboundMethod, "source_location", 
rb_method_location, 0);
+    rb_define_method(rb_cUnboundMethod, "parameters", 
rb_method_parameters, 0);

     /* Module#*_method */
Index: test/ruby/test_method.rb
===================================================================
--- test/ruby/test_method.rb  (revision 20232)
+++ test/ruby/test_method.rb  (working copy)
@@ -21,4 +21,6 @@ class TestMethod < Test::Unit::TestCase
   def mo5(a, *b, c) end
   def mo6(a, *b, c, &d) end
+  def mo7(a, b = nil, *c, d, &e) end
+  def ma1((a), &b) end

   class Base
@@ -222,3 +224,71 @@ class TestMethod < Test::Unit::TestCase
     assert_raise(ArgumentError) { o.method(:foo).call(1) }
   end
+
+  define_method(:pm0) {||}
+  define_method(:pm1) {|a|}
+  define_method(:pm2) {|a, b|}
+  define_method(:pmo1) {|a = nil, &b|}
+  define_method(:pmo2) {|a, b = nil|}
+  define_method(:pmo3) {|*a|}
+  define_method(:pmo4) {|a, *b, &c|}
+  define_method(:pmo5) {|a, *b, c|}
+  define_method(:pmo6) {|a, *b, c, &d|}
+  define_method(:pmo7) {|a, b = nil, *c, d, &e|}
+  define_method(:pma1) {|(a), &b|}
+
+  def test_bound_parameters
+    assert_equal(%w(), method(:m0).parameters)
+    assert_equal(%w(a), method(:m1).parameters)
+    assert_equal(%w(a b), method(:m2).parameters)
+    assert_equal(%w(?a &b), method(:mo1).parameters)
+    assert_equal(%w(a ?b), method(:mo2).parameters)
+    assert_equal(%w(*a), method(:mo3).parameters)
+    assert_equal(%w(a *b &c), method(:mo4).parameters)
+    assert_equal(%w(a *b c), method(:mo5).parameters)
+    assert_equal(%w(a *b c &d), method(:mo6).parameters)
+    assert_equal(%w(a ?b *c d &e), method(:mo7).parameters)
+    assert_equal([nil, "&b"], method(:ma1).parameters)
+  end
+
+  def test_unbound_parameters
+    assert_equal(%w(), self.class.instance_method(:m0).parameters)
+    assert_equal(%w(a), self.class.instance_method(:m1).parameters)
+    assert_equal(%w(a b), self.class.instance_method(:m2).parameters)
+    assert_equal(%w(?a &b), 
self.class.instance_method(:mo1).parameters)
+    assert_equal(%w(a ?b), self.class.instance_method(:mo2).parameters)
+    assert_equal(%w(*a), self.class.instance_method(:mo3).parameters)
+    assert_equal(%w(a *b &c), 
self.class.instance_method(:mo4).parameters)
+    assert_equal(%w(a *b c), 
self.class.instance_method(:mo5).parameters)
+    assert_equal(%w(a *b c &d), 
self.class.instance_method(:mo6).parameters)
+    assert_equal(%w(a ?b *c d &e), 
self.class.instance_method(:mo7).parameters)
+    assert_equal([nil, "&b"], 
self.class.instance_method(:ma1).parameters)
+  end
+
+  def test_bmethod_bound_parameters
+    assert_equal(%w(), method(:pm0).parameters)
+    assert_equal(%w(a), method(:pm1).parameters)
+    assert_equal(%w(a b), method(:pm2).parameters)
+    assert_equal(%w(?a &b), method(:pmo1).parameters)
+    assert_equal(%w(a ?b), method(:pmo2).parameters)
+    assert_equal(%w(*a), method(:pmo3).parameters)
+    assert_equal(%w(a *b &c), method(:pmo4).parameters)
+    assert_equal(%w(a *b c), method(:pmo5).parameters)
+    assert_equal(%w(a *b c &d), method(:pmo6).parameters)
+    assert_equal(%w(a ?b *c d &e), method(:pmo7).parameters)
+    assert_equal([nil, "&b"], method(:pma1).parameters)
+  end
+
+  def test_bmethod_unbound_parameters
+    assert_equal(%w(), self.class.instance_method(:pm0).parameters)
+    assert_equal(%w(a), self.class.instance_method(:pm1).parameters)
+    assert_equal(%w(a b), self.class.instance_method(:pm2).parameters)
+    assert_equal(%w(?a &b), 
self.class.instance_method(:pmo1).parameters)
+    assert_equal(%w(a ?b), 
self.class.instance_method(:pmo2).parameters)
+    assert_equal(%w(*a), self.class.instance_method(:pmo3).parameters)
+    assert_equal(%w(a *b &c), 
self.class.instance_method(:pmo4).parameters)
+    assert_equal(%w(a *b c), 
self.class.instance_method(:pmo5).parameters)
+    assert_equal(%w(a *b c &d), 
self.class.instance_method(:pmo6).parameters)
+    assert_equal(%w(a ?b *c d &e), 
self.class.instance_method(:pmo7).parameters)
+    assert_equal([nil, "&b"], 
self.class.instance_method(:pma1).parameters)
+  end
 end
Index: test/ruby/test_proc.rb
===================================================================
--- test/ruby/test_proc.rb  (revision 20232)
+++ test/ruby/test_proc.rb  (working copy)
@@ -674,3 +674,46 @@ class TestProc < Test::Unit::TestCase
     assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]")
   end
+
+  def test_parameters
+    assert_equal(%w(), proc {}.parameters)
+    assert_equal(%w(), proc {||}.parameters)
+    assert_equal(%w(a), proc {|a|}.parameters)
+    assert_equal(%w(a b), proc {|a, b|}.parameters)
+    assert_equal(%w(?a &b), proc {|a=:a, &b|}.parameters)
+    assert_equal(%w(a ?b), proc {|a, b=:b|}.parameters)
+    assert_equal(%w(*a), proc {|*a|}.parameters)
+    assert_equal(%w(a *b &c), proc {|a, *b, &c|}.parameters)
+    assert_equal(%w(a *b c), proc {|a, *b, c|}.parameters)
+    assert_equal(%w(a *b c &d), proc {|a, *b, c, &d|}.parameters)
+    assert_equal(%w(a ?b *c d &e), proc {|a, b=:b, *c, d, 
&e|}.parameters)
+    assert_equal([nil, "&b"], proc {|(a), &b|}.parameters)
+    assert_equal(%w[a b ?c ?d *e f g &h], proc 
{|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters)
+  end
+
+  def pm0() end
+  def pm1(a) end
+  def pm2(a, b) end
+  def pmo1(a = nil, &b) end
+  def pmo2(a, b = nil) end
+  def pmo3(*a) end
+  def pmo4(a, *b, &c) end
+  def pmo5(a, *b, c) end
+  def pmo6(a, *b, c, &d) end
+  def pmo7(a, b = nil, *c, d, &e) end
+  def pma1((a), &b) end
+
+
+  def test_bound_parameters
+    assert_equal(%w(), method(:pm0).to_proc.parameters)
+    assert_equal(%w(a), method(:pm1).to_proc.parameters)
+    assert_equal(%w(a b), method(:pm2).to_proc.parameters)
+    assert_equal(%w(?a &b), method(:pmo1).to_proc.parameters)
+    assert_equal(%w(a ?b), method(:pmo2).to_proc.parameters)
+    assert_equal(%w(*a), method(:pmo3).to_proc.parameters)
+    assert_equal(%w(a *b &c), method(:pmo4).to_proc.parameters)
+    assert_equal(%w(a *b c), method(:pmo5).to_proc.parameters)
+    assert_equal(%w(a *b c &d), method(:pmo6).to_proc.parameters)
+    assert_equal(%w(a ?b *c d &e), method(:pmo7).to_proc.parameters)
+    assert_equal([nil, "&b"], method(:pma1).to_proc.parameters)
+  end
 end
Posted by Charles Oliver Nutter (Guest)
on 14.11.2008 07:34
(Received via mailing list)
Nobuyoshi Nakada wrote:
> 
> It's simplest.

The original proposal also asked for literal defaults to be present. I'm
not sure that's possible with this version. I think I like a version
that doesn't require examining/mutating the string as well.

With this:

[[:req, 'a'], [:opt, 'b', 1]]

Building a list of params is a simple matter of...

params.each do |p|
   case p[0]
   when :req
     # do something with p[1]
   ...

...and creates no new objects at all.

With the encoded string version, it would require:

params.each do |p|
   case p[0] # new single-char string
   when '?'
     name = p[1..-1] # new string has to be sliced out
   ...
   else
     # else case to handle required args with no leading char...
     # == slowest possible processing for most common case
     name = p
   end
end

The first version is lighter-weight and the cases can be ordered to
favor the common case of required args.

>>    define_method(:bar) { |a,(b,c)|    }
>>
>>      => ["a", ["b", "c"]]
> 
> Is it really necessary?

I have doubts myself, but I won't vote one way or another.

- Charlie
Posted by Nobuyoshi Nakada (nobu)
on 14.11.2008 08:43
(Received via mailing list)
Hi,

At Fri, 14 Nov 2008 15:30:44 +0900,
Charles Oliver Nutter wrote in [ruby-core:19928]:
> 
> The original proposal also asked for literal defaults to be present. I'm 
> not sure that's possible with this version. I think I like a version 
> that doesn't require examining/mutating the string as well.

In [ruby-core:19838], Dave Thomas asked for what it is needed,
but I can't the answer from the original poster. Or I'm
missing?

>    ...
> 
> ...and creates no new objects at all.

Except for the element arrays of the result.  Anyway, it's a
trivial change.

> >>    define_method(:bar) { |a,(b,c)|    }
> >>
> >>      => ["a", ["b", "c"]]
> > 
> > Is it really necessary?
> 
> I have doubts myself, but I won't vote one way or another.

I wonder if the nested variables are considered in same level
of ordinary arguments, and it needs deep analysis of bytecode.
Posted by Nobuyoshi Nakada (nobu)
on 14.11.2008 08:46
(Received via mailing list)
Hi,

At Wed, 12 Nov 2008 05:12:44 +0900,
Paul Brannan wrote in [ruby-core:19842]:
> >   And as 1.9 merges bytecodes of those expressions into the
> >   method body, so I don't think it's possible to discompile.
> 
> The instructions are in the method body, but are pointed to by the
> arg_opt_table.  Each default value expression can be calculated by
> decompiling the corresponding instructions in the table.

But the code cut off from the context has no meanings.
Posted by Brian Candler (candlerb)
on 14.11.2008 09:37
(Received via mailing list)
On Fri, Nov 14, 2008 at 03:09:52PM +0900, Nobuyoshi Nakada wrote:
> 
> It's simplest.
> 
> >    define_method(:bar) { |a,(b,c)|    }
> > 
> >      => ["a", ["b", "c"]]
> 
> Is it really necessary?

Probably not. Maybe ["a", "()"], otherwise ["a", nil] as you propose.
Posted by Brian Candler (candlerb)
on 14.11.2008 09:51
(Received via mailing list)
On Fri, Nov 14, 2008 at 03:30:44PM +0900, Charles Oliver Nutter wrote:
> The original proposal also asked for literal defaults to be present.

True - but it's not clear if, or how, the literal defaults are used in 
merb.

Since "=" is not a valid character in a local variable, you could have a
string-based API which accommodates defaults:

  def action(a, b, c=123, d="hello #{c}", *e)

  => ["a", "b", "c=123", "d=\"hello #{c}\"", "e=*"]

But if this information is more expensive to collect and to split out, 
and
doesn't have a defined purpose, then I think it should be excluded.

I don't think anyone's proposing this is an interface to be used by rdoc 
or
other tools which need a full parse. In Merb's case, it's so that an
incoming parameters hash of

   {:a => "foo", :b => "bar", :x => "ignoreme"}

can be passed to the action() method in the correct positions. It's a
poor-man's named arguments.
Posted by Yehuda Katz (wycats)
on 14.11.2008 10:36
(Received via mailing list)
We need the defaults to handle out-of-order defaults:

def action(bar=1, baz=2); end

Where we get {baz => 5} inbound.

Sent from my iPhone
Posted by Brian Candler (candlerb)
on 14.11.2008 11:12
(Received via mailing list)
On Fri, Nov 14, 2008 at 06:32:58PM +0900, Yehuda Katz wrote:
> We need the defaults to handle out-of-order defaults:
>
> def action(bar=1, baz=2); end
>
> Where we get {baz => 5} inbound.

Ugh. Now I understand; instead of using Ruby's built-in default 
handling,
Merb has to provide a separate implementation.

And yet, if another action calls the controller method directly, it will 
be
using Ruby's default handling. This feels wrong somehow.

I suppose there could be a version of __send__ where you mark 
individually
which arguments are being passed real values, and which should fall back 
to
the default. Or even a magic object which means "use the default for 
this
argument". But you want the same thing to work across lots of different 
Ruby
implementations, and with minimal changes to them all.

Back to the drawing board...
Posted by Paul Brannan (cout)
on 14.11.2008 16:17
(Received via mailing list)
On Fri, Nov 14, 2008 at 04:42:07PM +0900, Nobuyoshi Nakada wrote:
> 
> But the code cut off from the context has no meanings.

Do you have an example?

Paul
Posted by Yehuda Katz (wycats)
on 15.11.2008 03:59
(Received via mailing list)
You were on the mark when you said it was a poor man's named args.
Real named args would solve the problem.

Sent from my iPhone
Posted by Yukihiro Matsumoto (Guest)
on 15.11.2008 16:48
(Received via mailing list)
Hi,

In message "Re: [ruby-core:19883] Re: Proposal: Method#get_args"
    on Thu, 13 Nov 2008 02:37:24 +0900, Yehuda Katz <wycats@gmail.com> 
writes:

|I am strongly in favor of this proposal. Getting something simple that  
|works and is futureproof would be fantastic.
|
|Matz, does this satisfy your initial concerns?

I guess so.  I'd like to make sure two more things:

  * symbol for block argument should be :block, or anything else?
  * if you don't have a rest argument name, you will get [:rest], ok?

              matz.
Posted by Yukihiro Matsumoto (Guest)
on 15.11.2008 16:52
(Received via mailing list)
Hi,

In message "Re: [ruby-core:19941] Re: Proposal: Method#get_args"
    on Sun, 16 Nov 2008 00:44:50 +0900, Yukihiro Matsumoto 
<matz@ruby-lang.org> writes:

||Matz, does this satisfy your initial concerns?
|
|I guess so.  I'd like to make sure two more things:
|
|  * symbol for block argument should be :block, or anything else?
|  * if you don't have a rest argument name, you will get [:rest], ok?

One more:

> def foo(a, b = 1, *c, d)
> => [[:req, :a], [:opt, :b, 1], [:rest, :c], [:req, :d]]

what if the default value for an optional argument is more than a simple
literal?   :expr as Austine mentioned in [ruby-core:19882]?  Or drop
default values altogether?

              matz.
Posted by Radosław Bułat (radarek)
on 15.11.2008 17:27
(Received via mailing list)
Yehuda, how do you handle default values like this?

class Application
  def action(a, b = default)
  end

protected
  def default
    2
  end
end

--
Pozdrawiam

Radosław Bułat
http://radarek.jogger.pl - mój blog
Posted by Nobuyoshi Nakada (nobu)
on 15.11.2008 21:56
(Received via mailing list)
Hi,

At Sun, 16 Nov 2008 00:48:54 +0900,
Yukihiro Matsumoto wrote in [ruby-core:19942]:
> what if the default value for an optional argument is more than a simple
> literal?   :expr as Austine mentioned in [ruby-core:19882]?  Or drop
> default values altogether?

Since :expr is a valid expression, I don't think it is
reasonable.


Index: iseq.c
===================================================================
--- iseq.c  (revision 20237)
+++ iseq.c  (working copy)
@@ -1275,4 +1275,67 @@ rb_iseq_clone(VALUE iseqval, VALUE newcb
 }

+static VALUE
+simple_default_value(const VALUE *seq)
+{
+    VALUE val;
+    switch (*seq++) {
+      case BIN(putnil):
+  val = Qnil;
+      case BIN(putobject):
+  val = *seq++;
+  if (*seq == BIN(setlocal)) break;
+      default:
+  return Qundef;
+    }
+    return val;
+}
+
+VALUE
+rb_iseq_parameters(const rb_iseq_t *iseq)
+{
+    int i, r, s;
+    VALUE a, args = rb_ary_new2(iseq->arg_size);
+    ID req, opt, rest, block;
+#define PARAM_TYPE(type) rb_ary_push(a = rb_ary_new2(2), ID2SYM(type))
+#define PARAM_ID(i) iseq->local_table[i]
+#define PARAM(i, type) (          \
+  PARAM_TYPE(type),          \
+  rb_id2name(PARAM_ID(i)) ?        \
+  rb_ary_push(a, ID2SYM(PARAM_ID(i))) : \
+  a)
+
+    CONST_ID(req, "req");
+    for (i = 0; i < iseq->argc; i++) {
+  rb_ary_push(args, PARAM(i, req));
+    }
+    r = iseq->arg_rest != -1 ? iseq->arg_rest :
+  iseq->arg_post_len > 0 ? iseq->arg_post_start :
+  iseq->arg_block != -1 ? iseq->arg_block :
+  iseq->arg_size;
+    CONST_ID(opt, "opt");
+    for (s = i; i < r; i++) {
+  PARAM_TYPE(opt);
+  if (rb_id2name(PARAM_ID(i))) {
+      VALUE defval = simple_default_value(iseq->iseq + 
iseq->arg_opt_table[i-s]);
+      rb_ary_push(a, ID2SYM(PARAM_ID(i)));
+      if (defval != Qundef) rb_ary_push(a, defval);
+  }
+  rb_ary_push(args, a);
+    }
+    if (iseq->arg_rest != -1) {
+  CONST_ID(rest, "rest");
+  rb_ary_push(args, PARAM(iseq->arg_rest, rest));
+    }
+    r = iseq->arg_post_start + iseq->arg_post_len;
+    for (i = iseq->arg_post_start; i < r; i++) {
+  rb_ary_push(args, PARAM(i, req));
+    }
+    if (iseq->arg_block != -1) {
+  CONST_ID(block, "block");
+  rb_ary_push(args, PARAM(iseq->arg_block, block));
+    }
+    return args;
+}
+
 /* ruby2cext */

Index: proc.c
===================================================================
--- proc.c  (revision 20237)
+++ proc.c  (working copy)
@@ -26,7 +26,10 @@ VALUE rb_cBinding;
 VALUE rb_cProc;

+VALUE rb_iseq_parameters(const rb_iseq_t *iseq);
+
 static VALUE bmcall(VALUE, VALUE);
 static int method_arity(VALUE);
 static VALUE rb_obj_is_method(VALUE m);
+static rb_iseq_t *get_method_iseq(VALUE method);

 /* Proc */
@@ -616,6 +619,12 @@ get_proc_iseq(VALUE self)
     GetProcPtr(self, proc);
     iseq = proc->block.iseq;
-    if (!RUBY_VM_NORMAL_ISEQ_P(iseq))
-  return 0;
+    if (!RUBY_VM_NORMAL_ISEQ_P(iseq)) {
+  NODE *node = (NODE *)iseq;
+  iseq = 0;
+  if (nd_type(node) == NODE_IFUNC && node->nd_cfnc == bmcall) {
+      /* method(:foo).to_proc */
+      iseq = get_method_iseq(node->nd_tval);
+  }
+    }
     return iseq;
 }
@@ -651,4 +660,40 @@ rb_proc_location(VALUE self)
 }

+static VALUE
+unnamed_parameters(int arity)
+{
+    VALUE a, param = rb_ary_new2((arity < 0) ? -arity : arity);
+    int n = (arity < 0) ? ~arity : arity;
+    ID req, rest;
+    CONST_ID(req, "req");
+    a = rb_ary_new3(1, ID2SYM(req));
+    OBJ_FREEZE(a);
+    for (; n; --n) {
+  rb_ary_push(param, a);
+    }
+    if (arity < 0) {
+  CONST_ID(rest, "rest");
+  rb_ary_store(param, ~arity, rb_ary_new3(1, ID2SYM(rest)));
+    }
+    return param;
+}
+
+/*
+ * call-seq:
+ *    proc.parameters  => array
+ *
+ * returns the parameter information of this proc
+ */
+
+static VALUE
+rb_proc_parameters(VALUE self)
+{
+    rb_iseq_t *iseq = get_proc_iseq(self);
+    if (!iseq) {
+  return unnamed_parameters(proc_arity(self));
+    }
+    return rb_iseq_parameters(iseq);
+}
+
 /*
  * call-seq:
@@ -1461,4 +1506,6 @@ get_method_iseq(VALUE method)
     body = data->body;
     switch (nd_type(body)) {
+      case NODE_BMETHOD:
+  return get_proc_iseq(body->nd_cval);
       case RUBY_VM_METHOD_NODE:
   GetISeqPtr((VALUE)body->nd_body, iseq);
@@ -1485,4 +1532,21 @@ rb_method_location(VALUE method)

 /*
+ * call-seq:
+ *    meth.parameters  => array
+ *
+ * returns the parameter information of this method
+ */
+
+static VALUE
+rb_method_parameters(VALUE method)
+{
+    rb_iseq_t *iseq = get_method_iseq(method);
+    if (!iseq) {
+  return unnamed_parameters(method_arity(method));
+    }
+    return rb_iseq_parameters(iseq);
+}
+
+/*
  *  call-seq:
  *   meth.to_s      =>  string
@@ -1812,4 +1876,5 @@ Init_Proc(void)
     rb_define_method(rb_cProc, "curry", proc_curry, -1);
     rb_define_method(rb_cProc, "source_location", rb_proc_location, 0);
+    rb_define_method(rb_cProc, "parameters", rb_proc_parameters, 0);

     /* Exceptions */
@@ -1847,4 +1912,5 @@ Init_Proc(void)
     rb_define_method(rb_cMethod, "unbind", method_unbind, 0);
     rb_define_method(rb_cMethod, "source_location", rb_method_location, 
0);
+    rb_define_method(rb_cMethod, "parameters", rb_method_parameters, 
0);
     rb_define_method(rb_mKernel, "method", rb_obj_method, 1);
     rb_define_method(rb_mKernel, "public_method", rb_obj_public_method, 
1);
@@ -1865,4 +1931,5 @@ Init_Proc(void)
     rb_define_method(rb_cUnboundMethod, "bind", umethod_bind, 1);
     rb_define_method(rb_cUnboundMethod, "source_location", 
rb_method_location, 0);
+    rb_define_method(rb_cUnboundMethod, "parameters", 
rb_method_parameters, 0);

     /* Module#*_method */
Index: test/ruby/test_proc.rb
===================================================================
--- test/ruby/test_proc.rb  (revision 20237)
+++ test/ruby/test_proc.rb  (working copy)
@@ -674,3 +674,46 @@ class TestProc < Test::Unit::TestCase
     assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]")
   end
+
+  def test_parameters
+    assert_equal([], proc {}.parameters)
+    assert_equal([], proc {||}.parameters)
+    assert_equal([[:req, :a]], proc {|a|}.parameters)
+    assert_equal([[:req, :a], [:req, :b]], proc {|a, b|}.parameters)
+    assert_equal([[:opt, :a, :a], [:block, :b]], proc {|a=:a, 
&b|}.parameters)
+    assert_equal([[:req, :a], [:opt, :b, :b]], proc {|a, 
b=:b|}.parameters)
+    assert_equal([[:rest, :a]], proc {|*a|}.parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:block, :c]], proc {|a, *b, 
&c|}.parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c]], proc {|a, *b, 
c|}.parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], 
proc {|a, *b, c, &d|}.parameters)
+    assert_equal([[:req, :a], [:opt, :b, :b], [:rest, :c], [:req, :d], 
[:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters)
+    assert_equal([[:req], [:block, :b]], proc {|(a), &b|}.parameters)
+    assert_equal([[:req, :a], [:req, :b], [:opt, :c, :c], [:opt, :d, 
:d], [:rest, :e], [:req, :f], [:req, :g], [:block, :h]], proc 
{|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters)
+  end
+
+  def pm0() end
+  def pm1(a) end
+  def pm2(a, b) end
+  def pmo1(a = :a, &b) end
+  def pmo2(a, b = :b) end
+  def pmo3(*a) end
+  def pmo4(a, *b, &c) end
+  def pmo5(a, *b, c) end
+  def pmo6(a, *b, c, &d) end
+  def pmo7(a, b = :b, *c, d, &e) end
+  def pma1((a), &b) end
+
+
+  def test_bound_parameters
+    assert_equal([], method(:pm0).to_proc.parameters)
+    assert_equal([[:req, :a]], method(:pm1).to_proc.parameters)
+    assert_equal([[:req, :a], [:req, :b]], 
method(:pm2).to_proc.parameters)
+    assert_equal([[:opt, :a, :a], [:block, :b]], 
method(:pmo1).to_proc.parameters)
+    assert_equal([[:req, :a], [:opt, :b, :b]], 
method(:pmo2).to_proc.parameters)
+    assert_equal([[:rest, :a]], method(:pmo3).to_proc.parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:block, :c]], 
method(:pmo4).to_proc.parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c]], 
method(:pmo5).to_proc.parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], 
method(:pmo6).to_proc.parameters)
+    assert_equal([[:req, :a], [:opt, :b, :b], [:rest, :c], [:req, :d], 
[:block, :e]], method(:pmo7).to_proc.parameters)
+    assert_equal([[:req], [:block, :b]], 
method(:pma1).to_proc.parameters)
+  end
 end
Index: test/ruby/test_method.rb
===================================================================
--- test/ruby/test_method.rb  (revision 20237)
+++ test/ruby/test_method.rb  (working copy)
@@ -21,4 +21,6 @@ class TestMethod < Test::Unit::TestCase
   def mo5(a, *b, c) end
   def mo6(a, *b, c, &d) end
+  def mo7(a, b = nil, *c, d, &e) end
+  def ma1((a), &b) end

   class Base
@@ -222,3 +224,71 @@ class TestMethod < Test::Unit::TestCase
     assert_raise(ArgumentError) { o.method(:foo).call(1) }
   end
+
+  define_method(:pm0) {||}
+  define_method(:pm1) {|a|}
+  define_method(:pm2) {|a, b|}
+  define_method(:pmo1) {|a = nil, &b|}
+  define_method(:pmo2) {|a, b = nil|}
+  define_method(:pmo3) {|*a|}
+  define_method(:pmo4) {|a, *b, &c|}
+  define_method(:pmo5) {|a, *b, c|}
+  define_method(:pmo6) {|a, *b, c, &d|}
+  define_method(:pmo7) {|a, b = nil, *c, d, &e|}
+  define_method(:pma1) {|(a), &b|}
+
+  def test_bound_parameters
+    assert_equal([], method(:m0).parameters)
+    assert_equal([[:req, :a]], method(:m1).parameters)
+    assert_equal([[:req, :a], [:req, :b]], method(:m2).parameters)
+    assert_equal([[:opt, :a, nil], [:block, :b]], 
method(:mo1).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil]], 
method(:mo2).parameters)
+    assert_equal([[:rest, :a]], method(:mo3).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:block, :c]], 
method(:mo4).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c]], 
method(:mo5).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], 
method(:mo6).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil], [:rest, :c], [:req, :d], 
[:block, :e]], method(:mo7).parameters)
+    assert_equal([[:req], [:block, :b]], method(:ma1).parameters)
+  end
+
+  def test_unbound_parameters
+    assert_equal([], self.class.instance_method(:m0).parameters)
+    assert_equal([[:req, :a]], 
self.class.instance_method(:m1).parameters)
+    assert_equal([[:req, :a], [:req, :b]], 
self.class.instance_method(:m2).parameters)
+    assert_equal([[:opt, :a, nil], [:block, :b]], 
self.class.instance_method(:mo1).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil]], 
self.class.instance_method(:mo2).parameters)
+    assert_equal([[:rest, :a]], 
self.class.instance_method(:mo3).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:block, :c]], 
self.class.instance_method(:mo4).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c]], 
self.class.instance_method(:mo5).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], 
self.class.instance_method(:mo6).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil], [:rest, :c], [:req, :d], 
[:block, :e]], self.class.instance_method(:mo7).parameters)
+    assert_equal([[:req], [:block, :b]], 
self.class.instance_method(:ma1).parameters)
+  end
+
+  def test_bmethod_bound_parameters
+    assert_equal([], method(:pm0).parameters)
+    assert_equal([[:req, :a]], method(:pm1).parameters)
+    assert_equal([[:req, :a], [:req, :b]], method(:pm2).parameters)
+    assert_equal([[:opt, :a, nil], [:block, :b]], 
method(:pmo1).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil]], 
method(:pmo2).parameters)
+    assert_equal([[:rest, :a]], method(:pmo3).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:block, :c]], 
method(:pmo4).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c]], 
method(:pmo5).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], 
method(:pmo6).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil], [:rest, :c], [:req, :d], 
[:block, :e]], method(:pmo7).parameters)
+    assert_equal([[:req], [:block, :b]], method(:pma1).parameters)
+  end
+
+  def test_bmethod_unbound_parameters
+    assert_equal([], self.class.instance_method(:pm0).parameters)
+    assert_equal([[:req, :a]], 
self.class.instance_method(:pm1).parameters)
+    assert_equal([[:req, :a], [:req, :b]], 
self.class.instance_method(:pm2).parameters)
+    assert_equal([[:opt, :a, nil], [:block, :b]], 
self.class.instance_method(:pmo1).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil]], 
self.class.instance_method(:pmo2).parameters)
+    assert_equal([[:rest, :a]], 
self.class.instance_method(:pmo3).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:block, :c]], 
self.class.instance_method(:pmo4).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c]], 
self.class.instance_method(:pmo5).parameters)
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], 
self.class.instance_method(:pmo6).parameters)
+    assert_equal([[:req, :a], [:opt, :b, nil], [:rest, :c], [:req, :d], 
[:block, :e]], self.class.instance_method(:pmo7).parameters)
+    assert_equal([[:req], [:block, :b]], 
self.class.instance_method(:pma1).parameters)
+  end
 end
Posted by Nobuyoshi Nakada (nobu)
on 15.11.2008 22:27
(Received via mailing list)
Hi,

At Sat, 15 Nov 2008 00:14:00 +0900,
Paul Brannan wrote in [ruby-core:19937]:
> > > >   And as 1.9 merges bytecodes of those expressions into the
> > > >   method body, so I don't think it's possible to discompile.
> > > 
> > > The instructions are in the method body, but are pointed to by the
> > > arg_opt_table.  Each default value expression can be calculated by
> > > decompiling the corresponding instructions in the table.
> > 
> > But the code cut off from the context has no meanings.
> 
> Do you have an example?

The code to set default value accesses the local variable, that
is it contains the index of the variable.  This index has
meaning only in that context.
Posted by Yehuda Katz (wycats)
on 15.11.2008 23:16
(Received via mailing list)
We don't need non-simple values. Something like :expr would work me
(except for conflicts with the literal :expr symbol)

Sent from my iPhone

On Nov 15, 2008, at 8:23 AM, "Radosław Bułat" <radek.bulat@gmail.com>
Posted by Yehuda Katz (wycats)
on 15.11.2008 23:20
(Received via mailing list)
Agreed. What we need is a special sentinel value (I originally
proposed Method::EXPRESSION)

Sent from my iPhone

On Nov 15, 2008, at 12:53 PM, Nobuyoshi Nakada <nobu@ruby-lang.org>
Posted by Roger Pack (rogerdpack)
on 15.11.2008 23:37
(Received via mailing list)
> We need the defaults to handle out-of-order defaults:
> def action(bar=1, baz=2); end
> Where we get {baz => 5} inbound.

AFAIK this isn't possible in Ruby currently (though might work fine
with "simple" values like you said).

Some notes:

Note that order matters:
def action(bar=1, bar2 = bar1); end # valid but note that it needs
bar1 to work, and needs it first

def action(bar2 = bar1, bar1=0); end # invalid

And then the "edge cases" where definitions include assignment are hard:

def action(bar=1, baz=(hidden_variable=2), foo=2*baz); end

which, if you are given bar and foo, you can't recreate easily that
hidden_variable == 2

What you want might be something along the lines of
object.__send_with_named_variables__ :action, :baz => 5
though I'm not sure how it would work.

With access to the AST you can write "wrappers" to methods [which
still wouldn't help with the hidden_variable fella, except raising if
you find it, but work besides that] [1].

Was there a plan for named parameters in 2.0?  That might be the way to 
go.


-=R

[1] 
http://code.google.com/p/ruby-roger-useful-functions/wiki/NamedParameters
Posted by Yehuda Katz (wycats)
on 16.11.2008 00:21
(Received via mailing list)
In our case, we're dealing with inbound web params, so the defaults
are always strings.

Sent from my iPhone

On Nov 15, 2008, at 2:34 PM, "Roger Pack" <rogerpack2005@gmail.com>
Posted by Yehuda Katz (wycats)
on 16.11.2008 00:54
(Received via mailing list)
Just to be clear on the use-case:
The idea is that having better introspection into Ruby methods could 
help
map inbound parameters (for, say, XML-RPC, REST-based params, or some 
other
RPC protocol) to the Ruby class. For this purpose, being able to define 
only
literal defaults is just fine, as inbound RPC or REST parameters are 
usually
limited to literals as well.

In the case of REST, inbound params are always Strings. I say we start 
by
exposing just the simple literal set we've already discussed, and add
support for additional literal types (Arrays of literals, for instance) 
if
there is strong demand.

-- Yehuda
Posted by Paul Brannan (cout)
on 16.11.2008 18:24
(Received via mailing list)
On Sun, Nov 16, 2008 at 06:23:05AM +0900, Nobuyoshi Nakada wrote:
> Paul Brannan wrote in [ruby-core:19937]:
> > > But the code cut off from the context has no meanings.
> > 
> > Do you have an example?
> 
> The code to set default value accesses the local variable, that
> is it contains the index of the variable.  This index has
> meaning only in that context.

I want to make a distinction between the code and the representation of
that code in memory.

A setlocal or getlocal does reference an index, but fundamentally this
is only how it is represented in the iseq.  Conceptually the instruction
refers to a name (and that name can be retrieved from the local variable
table).

Paul
Posted by Yehuda Katz (wycats)
on 21.11.2008 21:36
(Received via mailing list)
What values does simple_default handle? Assuming it covers the simple 
cases
we discussed is this ready to apply? Are there any objections?
-- Yehuda
Posted by Yukihiro Matsumoto (Guest)
on 25.11.2008 10:18
(Received via mailing list)
Hi,

In message "Re: [ruby-core:20029] Re: Proposal: Method#get_args"
    on Sat, 22 Nov 2008 05:31:09 +0900, "Yehuda Katz" <wycats@gmail.com> 
writes:
|
|[1  <text/plain; ISO-8859-1 (7bit)>]
|What values does simple_default handle? Assuming it covers the simple cases
|we discussed is this ready to apply? Are there any objections?
|-- Yehuda

The most realistic implementation is one from Nobu in
[ruby-core:19947], which ignores default values for optional
arguments.

  p ->(a,b=1,c=4,d){}.parameters
  # => [[:req, :a], [:opt, :b], [:opt, :c], [:req, :d]]

If it's OK, I will approve the patch.  The only concern will be we
currently have no parameter information for C implemented methods with
variable argument.

  p method(:printf).parameters
  # => [[:rest]]

              matz.
Posted by Yehuda Katz (wycats)
on 26.11.2008 01:33
(Received via mailing list)
It doesn't let me do 100% of what I want, but it does enough for me to 
be
happy.
:-D

I'll +1 the patch

-- Yehuda
Posted by Yukihiro Matsumoto (Guest)
on 26.11.2008 05:25
(Received via mailing list)
Hi,

In message "Re: [ruby-core:20107] Re: Proposal: Method#get_args"
    on Wed, 26 Nov 2008 09:28:06 +0900, "Yehuda Katz" <wycats@gmail.com> 
writes:

|It doesn't let me do 100% of what I want, but it does enough for me to be
|happy.
|:-D
|
|I'll +1 the patch

Nakada-san, could you check in?

              matz.
Posted by Paul McMahon (Guest)
on 04.12.2008 11:07
(Received via mailing list)
I am late to this discussion, but I am a bit concerned about the
handling of default arguments, as you seem to be returning the result
of the calculated default arguments every time you call get_args.  For
example:

def foo(bar=[]); end
method(:foo).get_args #=> [[[:bar, []]], nil, nil]

I am assuming everytime get_args gets called, a new array is created.
Now if the default argument is something like an array or string, this
isn't a problem.  However, the value for a default argument can be an
ruby statement, whose result is calculated each time the method is
invoked.  For instance, a contrived example is:

$i = 0
def foo(bar=($i +=1)); end
foo
$i #=> 1

Now, the way your get_args is working, if you call get args;

method(:foo).get_args #=> [[[:bar, 2], nil, nil]
$i #=> 2

This is probably unexpected and undesirable behaviour.

You could get around this by returning a Proc in the tuple, but
perhaps something more object oriented would be nicer in the first
place.

For example,

m = method(:foo)
m.splat_args? #=> false
m.proc? #=> false
args = m.arguments #=> [ Proc ]
args.first.call #=> 3

would be nicer to work with, for me at least.
Posted by Daniel Luz (Guest)
on 04.12.2008 14:08
(Received via mailing list)
On Thu, Dec 4, 2008 at 08:01, Paul McMahon <paul.mcmahon@ubit.com> 
wrote:
> I am late to this discussion, but I am a bit concerned about the
> handling of default arguments, as you seem to be returning the result
> of the calculated default arguments every time you call get_args.  For
> example:
>
> def foo(bar=[]); end
> method(:foo).get_args #=> [[[:bar, []]], nil, nil]

Actually, it returns [:opt, param_name, default_value], but default
values are only given for basic types (Numerics, Symbols, true, false
and nil, that I could see) anyway. For your example, it would return
just [[:opt, :foo]], no third element on the Array.

> You could get around this by returning a Proc in the tuple, but
> perhaps something more object oriented would be nicer in the first
> place.

As some already mentioned, a Proc would not work, because a default
value has a very particular binding: it runs in the context of the
receiver, and can reference all the parameters that come before
itself.

For example:

    def f(x, y = x * 2, z = x + y, w = some_private_method); end
Posted by Brian Candler (candlerb)
on 04.12.2008 14:46
(Received via mailing list)
On Thu, Dec 04, 2008 at 10:02:29PM +0900, Daniel Luz wrote:
> As some already mentioned, a Proc would not work, because a default
> value has a very particular binding: it runs in the context of the
> receiver, and can reference all the parameters that come before
> itself.

I suppose it could also (but currently can't) access mandatory 
parameters
which come after it:

  def foo(x, y=x+z, z) ... end

That way lies madness :-) Although I can't think of sensible use cases 
where
mandatory parameters are at both ends anyway.
Posted by Radosław Bułat (radarek)
on 04.12.2008 15:15
(Received via mailing list)
On Thu, Dec 4, 2008 at 2:40 PM, Brian Candler <B.Candler@pobox.com> 
wrote:
> Although I can't think of sensible use cases where
> mandatory parameters are at both ends anyway.
>

Ko1 posted several days ago. It was something like that:

class MyArray
  def []=(*args, value)
    p [args, value]
  end
end

MyArray.new[1, 2, 3, 4] = 5
# args = [1,2, 3, 4], value = 5

If it was only for that particular case the question is if it is worth 
it?
--
Pozdrawiam

Radoslaw Bulat
http://radarek.jogger.pl
Posted by Brian Candler (candlerb)
on 04.12.2008 15:33
(Received via mailing list)
On Thu, Dec 04, 2008 at 11:08:37PM +0900, Radosław Bułat wrote:
> 
> If it was only for that particular case the question is if it is worth it?

Indeed, and you could already do

  def []=(*args)
    value = args.pop
    p [args, value]
  end

My feeling is it may lead to inconsistent or less-predictable APIs; 
and/or
it will be widely ignored, and just become another wrinkle in the 
language.
But it's there now, and I guess it's an interesting experiment.

Regards,

Brian.