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.
on 10.11.2008 00:18
on 10.11.2008 02:29
+1 (or is that frowned upon?) I definitely think that this would be a great compliment to arity inspection... Matt
on 10.11.2008 05:36
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
on 10.11.2008 05:42
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.
on 10.11.2008 07:40
> 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
on 10.11.2008 10:29
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
on 10.11.2008 13:53
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
on 10.11.2008 14:57
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 :-)
on 10.11.2008 15:27
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" ]
on 10.11.2008 15:55
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
on 10.11.2008 16:05
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
on 10.11.2008 16:21
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
on 11.11.2008 01:12
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.
on 11.11.2008 01:22
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
on 11.11.2008 01:45
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.
on 11.11.2008 01:49
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.
on 11.11.2008 02:14
> 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
on 11.11.2008 04:52
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
on 11.11.2008 05:02
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!)
on 11.11.2008 05:08
> 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
on 11.11.2008 05:18
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
on 11.11.2008 07:20
> 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
on 11.11.2008 08:06
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
on 11.11.2008 08:28
> 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) ......
on 11.11.2008 12:04
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.
on 11.11.2008 15:22
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
on 11.11.2008 20:01
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.
on 11.11.2008 20:22
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
on 11.11.2008 20:24
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
on 11.11.2008 20:30
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
on 11.11.2008 20:31
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
on 11.11.2008 20:33
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
on 11.11.2008 20:36
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
on 11.11.2008 20:40
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
on 11.11.2008 20:51
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
on 11.11.2008 20:51
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
on 11.11.2008 21:16
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
on 11.11.2008 21:20
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
on 11.11.2008 21:42
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.
on 11.11.2008 21:46
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
on 12.11.2008 10:06
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)| ... }
on 12.11.2008 10:20
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.
on 12.11.2008 14:29
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
on 12.11.2008 15:16
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
on 12.11.2008 17:53
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.
on 12.11.2008 17:59
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
on 12.11.2008 18:10
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
on 12.11.2008 18:28
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
on 12.11.2008 18:40
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
on 12.11.2008 18:56
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
on 12.11.2008 19:17
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
on 12.11.2008 19:41
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
on 12.11.2008 19:50
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
on 12.11.2008 20:06
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
on 12.11.2008 20:26
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)" )
on 12.11.2008 20:36
> > 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.
on 12.11.2008 20:43
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.
on 12.11.2008 21:24
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)
on 12.11.2008 22:16
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
on 12.11.2008 22:30
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
on 12.11.2008 22:39
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
on 12.11.2008 23:05
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.
on 12.11.2008 23:51
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
on 13.11.2008 06:57
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
on 13.11.2008 10:50
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)
on 13.11.2008 10:51
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 :-)
on 13.11.2008 13:44
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.
on 14.11.2008 07:13
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
on 14.11.2008 07:34
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
on 14.11.2008 08:43
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.
on 14.11.2008 08:46
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.
on 14.11.2008 09:37
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.
on 14.11.2008 09:51
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.
on 14.11.2008 10:36
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
on 14.11.2008 11:12
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...
on 14.11.2008 16:17
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
on 15.11.2008 03:59
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
on 15.11.2008 16:48
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.
on 15.11.2008 16:52
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.
on 15.11.2008 17:27
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
on 15.11.2008 21:56
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
on 15.11.2008 22:27
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.
on 15.11.2008 23:16
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>
on 15.11.2008 23:20
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>
on 15.11.2008 23:37
> 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
on 16.11.2008 00:21
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>
on 16.11.2008 00:54
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
on 16.11.2008 18:24
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
on 21.11.2008 21:36
What values does simple_default handle? Assuming it covers the simple cases we discussed is this ready to apply? Are there any objections? -- Yehuda
on 25.11.2008 10:18
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.
on 26.11.2008 01:33
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
on 26.11.2008 05:25
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.
on 04.12.2008 11:07
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.
on 04.12.2008 14:08
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
on 04.12.2008 14:46
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.
on 04.12.2008 15:15
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
on 04.12.2008 15:33
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.