Argument Passing Syntax

G

gwtmp01

Why are arguments to the '[]' method parsed differently
than a standard method?

class A
def m1(*args)
puts args.inspect
end
def [](*args)
puts args.inspect
end
end

irb> a = A.new
=> #<A:0x321c28>
irb> a.m1(1,2)
[1, 2]
=> nil
irb> a.m1(1 => 2)
[{1=>2}]
=> nil
irb> a.m1(1, 2 => 3)
[1, {2=>3}]
=> nil
irb> a[1,2]
[1, 2]
=> nil
irb> a[1 => 2]
[{1=>2}]
=> nil
irb> a[1, 2 => 3]
SyntaxError: compile error
(irb):15: syntax error
a[1, 2 => 3]
^
from (irb):15

Gary Wright
 
D

dave.burt

I haven't seen that syntax error before. I don't see any reason why
that shouldn't parse OK - I'd call it a bug in the parser, unless Matz
says otherwise.

It's interesting because it should (IMHO) be possible to pass anonymous
and named parameters when calling a Proc, like so:

check["6 times 9", :answer => 42]

Cheers,
Dave
 
E

Eric Hodel

Why are arguments to the '[]' method parsed differently
than a standard method?
[snip]

irb> a.m1(1, 2 => 3)
[1, {2=>3}]
=> nil

uses call_args (and friends)
irb> a[1 => 2]
[{1=>2}]
=> nil

uses assoc_list, for Hash::[]
irb> a[1, 2 => 3]
SyntaxError: compile error
(irb):15: syntax error
a[1, 2 => 3]
^
from (irb):15

I imagine the second syntax was added only for Hash::[].
 
S

Sean O'Halpin

Why are arguments to the '[]' method parsed differently
than a standard method?

Because () and [] are parsed differently. [] is primarily an array
reference (aref).
You can't pass blocks either in 1.8. In 1.9 you will be able to do this:

class O
def [](*args, &block)
yield(args) if block_given?
end
end

o =3D O.new
o.[](1,2,3) { |x| p x } # this is what you have to do in 1.8
o[1,2,3] { |x| p x } # this causes syntax error in 1.8 but works in 1.9

but not this (as far as I know):

o[1,:var =3D> 3] # syntax error in 1.8 & 1.9

Regards,

Sean
 
G

gwtmp01

Why are arguments to the '[]' method parsed differently
than a standard method?

Because () and [] are parsed differently. [] is primarily an array
reference (aref).

But *why* are they parsed differently? Just "because"?

For example, the common technique of aliasing :[] to :new means you
can't expect to have ruby parse a trailing hash in the argument list
when the method is called via :[] but you can when called via :new.

I understand that the :[] method syntax has a different evolutionary
path than the standard method syntax but is that the only reason that
the arguments are parsed differently?
You can't pass blocks either in 1.8. In 1.9 you will be able to do
this:
o[1,2,3] { |x| p x } # this causes syntax error in 1.8 but works
in 1.9

Makes sense. I vote to extend the change to allow:

o[1,2,3,:key => val]

Note, you can already have an implicit literal hash, you just can't
precede
it with other arguments.

o[:key => val] # OK now.
o[1,2,3] # OK now.
o[1,2,3,:key=>val] # bad now.


Gary Wright
 
G

gwtmp01

o.[](1,2,3) { |x| p x } # this is what you have to do in 1.8
o[1,2,3] { |x| p x } # this causes syntax error in 1.8 but works
in 1.9

Here is a related thought.

Right now you can't have arguments to an "assignment method":

class A
def attribute1=(*args, &block)
@attr1 = value
end
end

a = A.new
a.attribute1 = 4 # ok
a.attribute1(4) = 5 # syntax error
a.attribute1=(4, 5) # syntax error
a.send:)attribute1=, 4,5) # ok

Why not allow the middle two cases? I think this would be helpful when
a class is wrapping one or more "containers" (Array, Hash, Set,...) and
you don't want to expose the entire interface of the container outside
of the class. You can do this reasonably well with :[] and :[]= for a
single container but you can't have methods such as :container2[] and
:container2[]= because of the parsing problems:

container2[index] = val # is this self.container2.[]
(index,val) or
# is this self.container2[](index,val)

But if you allowed for arguments to :container= then you could do
something
like:

container2(index) # lookup contents of @container2[index]
container2(index) = value # modify contents of @container2[index]

Right now if you want to package multiple containers in a class you need
to create set/get methods for each container that map to :[]/[]= for
the
container or insert some sort of proxy class to avoid exposing the
entire
container.

Is there a parsing hurdle to overcome or is there some conceptual reason
for prohibiting additional arguments to assignment methods?
 
G

gwtmp01

Hash::[] is the only reason [1 => 2] is allowed, see: ruby-talk:163033

I've looked through the parse.y file, so I understand the mechanical
reason why it doesn't work (it doesn't match the grammer) but I'm
not sure I've heard a design or philosophical reason why the grammar
is restrictive in this case. I haven't looked at it enough to
decide if extending the 'aref_args' construct would introduce
some ambiguity. It may be that it conflicts with the parsing of
literal arrays.



Gary Wright
 
E

Eric Hodel

Hash::[] is the only reason [1 => 2] is allowed, see: ruby-talk:
163033

I've looked through the parse.y file, so I understand the mechanical
reason why it doesn't work (it doesn't match the grammer) but I'm
not sure I've heard a design or philosophical reason why the grammar
is restrictive in this case. I haven't looked at it enough to
decide if extending the 'aref_args' construct would introduce
some ambiguity. It may be that it conflicts with the parsing of
literal arrays.

IIRC, #[] and #call doing the same thing is a (relatively) recent
addition to the language. Hash::[] has been in Ruby since the first
rev of Hash.

So it is possible it is simply an oversight.
 
G

gwtmp01

IIRC, #[] and #call doing the same thing is a (relatively) recent
addition to the language. Hash::[] has been in Ruby since the
first rev of Hash.

So it is possible it is simply an oversight.

This was my first thought about this also, which is why
I posed the question--I wondered if I was missing something.


Gary Wright
 
L

Leslie Viljoen

Hey people

Just had quite an experience deploying my Rails app to Textdrive -
the Nitro gem was installed and there was a class in the Glue module
with the same name as my model... it took me two days to figure it out!

What I don't understand is how Glue got included in my project, since
all the Rails code I looked at makes no mention of it and I didn't
'require' it. Running irb, the offending class was not there,
running script/console, it was included!

How do I track down where it was included? Is there a way to list the
files that have been 'require'd? Is there a way to list all classes
available -
and is there a way to tell if a class is 'custom' versus included with
the base Ruby installation?

Les
 
J

James Britt

Leslie said:
Hey people

Just had quite an experience deploying my Rails app to Textdrive -
the Nitro gem was installed and there was a class in the Glue module
with the same name as my model... it took me two days to figure it out!

What I don't understand is how Glue got included in my project, since
all the Rails code I looked at makes no mention of it and I didn't
'require' it. Running irb, the offending class was not there,
running script/console, it was included!

How do I track down where it was included? Is there a way to list the
files that have been 'require'd? Is there a way to list all classes
available -
and is there a way to tell if a class is 'custom' versus included with
the base Ruby installation?


warn $".join( "\n" )

Maybe replace 'warn' with a call to some logger.


James


--

http://www.ruby-doc.org - The Ruby Documentation Site
http://www.rubyxml.com - News, Articles, and Listings for Ruby & XML
http://www.rubystuff.com - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com - Playing with Better Toys
 
S

Stefan Lang

Hey people

Just had quite an experience deploying my Rails app to Textdrive -
the Nitro gem was installed and there was a class in the Glue
module with the same name as my model... it took me two days to
figure it out!

What I don't understand is how Glue got included in my project,
since all the Rails code I looked at makes no mention of it and I
didn't 'require' it. Running irb, the offending class was not
there, running script/console, it was included!

How do I track down where it was included? Is there a way to list
the files that have been 'require'd? Is there a way to list all
classes available -
and is there a way to tell if a class is 'custom' versus included
with the base Ruby installation?

Les

Save this in a file "loaded.rb":
--------------------------------
require 'rbconfig'

def find_ruby_lib(name)
$LOAD_PATH.each { |dir|
fn = File.join(dir, name)
return dir, fn if File.exist? fn
}
raise "no such library loaded -- #{name}"
end

def is_std_library_dir?(dir)
dir == Config::CONFIG["rubylibdir"] ||
dir == Config::CONFIG["archdir"]
end

def print_loaded_libs
$LOADED_FEATURES.each { |lib_name|
dir, fn = find_ruby_lib(lib_name)
puts lib_name
puts " path: #{fn}"
puts " Standard Library" if is_std_library_dir?(dir)
}
end
--------------------------------
Then, before exiting (or at least after all require's)
do:
require 'loaded'
print_loaded_libs

Example:
ruby -r optparse -r loaded -e 'print_loaded_libs'
optparse.rb
path: /usr/local/lib/ruby/1.8/optparse.rb
Standard Library
loaded.rb
path: ./loaded.rb
rbconfig.rb
path: /usr/local/lib/ruby/1.8/i686-linux/rbconfig.rb
Standard Library

HTH,
Stefan
 
L

Leslie Viljoen

Stefan said:
Hey people

Just had quite an experience deploying my Rails app to Textdrive -
the Nitro gem was installed and there was a class in the Glue
module with the same name as my model... it took me two days to
figure it out!

What I don't understand is how Glue got included in my project,
since all the Rails code I looked at makes no mention of it and I
didn't 'require' it. Running irb, the offending class was not
there, running script/console, it was included!

How do I track down where it was included? Is there a way to list
the files that have been 'require'd? Is there a way to list all
classes available -
and is there a way to tell if a class is 'custom' versus included
with the base Ruby installation?

Les

Save this in a file "loaded.rb":
--------------------------------
require 'rbconfig'

def find_ruby_lib(name)
$LOAD_PATH.each { |dir|
fn = File.join(dir, name)
return dir, fn if File.exist? fn
}
raise "no such library loaded -- #{name}"
end

def is_std_library_dir?(dir)
dir == Config::CONFIG["rubylibdir"] ||
dir == Config::CONFIG["archdir"]
end

def print_loaded_libs
$LOADED_FEATURES.each { |lib_name|
dir, fn = find_ruby_lib(lib_name)
puts lib_name
puts " path: #{fn}"
puts " Standard Library" if is_std_library_dir?(dir)
}
end
--------------------------------
Then, before exiting (or at least after all require's)
do:
require 'loaded'
print_loaded_libs

Example:
ruby -r optparse -r loaded -e 'print_loaded_libs'
optparse.rb
path: /usr/local/lib/ruby/1.8/optparse.rb
Standard Library
loaded.rb
path: ./loaded.rb
rbconfig.rb
path: /usr/local/lib/ruby/1.8/i686-linux/rbconfig.rb
Standard Library

HTH,
Stefan
Thanks - great solution!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top