#has_arguments?

I

Intransition

Messing with optional argument check for the umpteenth time, eg.

def meth(a=Exception)
if a != Exception
...
else
...
end
end

Other's might do:

def meth(*a)
if !a.empty?
a = a.first
...
else
...
end
end

Neither of which are very satisfying. So it occurs to me tonight that
we already have #has_block? to see if a block was passed. So how about
a #has_arguments? to query if _any_ arguments have been passed. So
then...

def meth(a=default)
if has_arguments?
...
else
...
end
end

Ah... now that would be nice.
 
R

Roger Pack

=A0def meth(*a)
=A0 =A0if !a.empty?
=A0 =A0 =A0a =3D a.first
=A0 =A0 =A0 ...
=A0 =A0else
=A0 =A0 =A0...
=A0 =A0end
=A0end


Maybe morph this to

def meth(*a)
if has_arguments? a
...
else
...
end
end

?
-r
 
R

Rick DeNatale

Messing with optional argument check for the umpteenth time, eg.

=A0def meth(a=3DException)
=A0 =A0if a !=3D Exception
=A0 =A0 =A0 ...
=A0 =A0else
=A0 =A0 =A0...
=A0 =A0end
=A0end

Other's might do:

=A0def meth(*a)
=A0 =A0if !a.empty?
=A0 =A0 =A0a =3D a.first
=A0 =A0 =A0 ...
=A0 =A0else
=A0 =A0 =A0...
=A0 =A0end
=A0end

Neither of which are very satisfying. So it occurs to me tonight that
we already have #has_block? to see if a block was passed. So how about
a #has_arguments? to query if _any_ arguments have been passed. So
then...

=A0def meth(a=3Ddefault)
=A0 =A0if has_arguments?
=A0 =A0 =A0 ...
=A0 =A0else
=A0 =A0 =A0...
=A0 =A0end
=A0end

Ah... now that would be nice.

I've never felt the need for this.

Normally I just make the default value for an optional parameter nil,
and check for nil.

If nil is a valid value, then I'd do something like

class A

MissingArgument =3D Object.new

def meth(a=3DMissingArgument)
if a =3D=3D MissingArgument
else
end
end
end




--=20
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
T

Trans

I've never felt the need for this.

Normally I just make the default value for an optional parameter nil,
and check for nil.

If nil is a valid value, then I'd do something like

Right, it precisely the times when nil is a valid value that it is a
problem.
class A

=A0 =A0 MissingArgument =3D Object.new

=A0 =A0def meth(a=3DMissingArgument)
=A0 =A0 =A0 =A0if a =3D=3D MissingArgument
=A0 =A0 =A0 =A0else
=A0 =A0 =A0 =A0end
=A0 =A0end
end

Hmm... doesn't that indicate a need? ;)

And yes, I've done that before too, but it falls into the realm of my
first example. Exception is typically just as good as MissingArgument,
and that way you don't need a stray object that exists just to exist.
But in either case it is still a (albeit minor) jerry-rig.

What we have here is a nail, and I'd much rather have a nice hammer
than to keep using this damn rock.
 
T

Trans

Maybe morph this to

def meth(*a)
=A0 if has_arguments? a
=A0 =A0 ...
=A0 else
=A0 =A0 ...
=A0 end
end

Either way is fine. The defined method interface doesn't matter.
#has_arguments? would simply report false if a call to the method
passed no arguments. If the interface definition itself does not allow
for no arguments, eg. 'def meth(a)', then #has_arguments? would simply
never return false.

T.
 
E

Eric Hodel

Right, it precisely the times when nil is a valid value that it is a
problem.

class X
NONE = Object.new

def blah(a = NONE)
# ...
end
end

Suffices for nil as valid value
 
M

Matthew K. Williams

As a followup, has_arguments? could be written as:

def has_arguments?(&b)
vars = eval("local_variables",b.binding)
return false if vars.length == 0
vars.each do |v|
return false if eval("#{v}.nil?",b)
end
return true
end
end


then you'd call it like:

if has_arguments? {} #note the empty block
something
end


Matt
 
C

Chris Shea

Messing with optional argument check for the umpteenth time, eg.

  def meth(a=Exception)
    if a != Exception
       ...
    else
      ...
    end
  end

Other's might do:

  def meth(*a)
    if !a.empty?
      a = a.first
       ...
    else
      ...
    end
  end

Neither of which are very satisfying. So it occurs to me tonight that
we already have #has_block? to see if a block was passed. So how about
a #has_arguments? to query if _any_ arguments have been passed. So
then...

  def meth(a=default)
    if has_arguments?
       ...
    else
      ...
    end
  end

Ah... now that would be nice.

Here's a little trick I learned (very hacky):

def meth(a=(no_argument='default'))
if no_argument
'nothing given'
else
"we got #{a.inspect}"
end
end

Your default argument can't be falsy (but you could use something
truey and use your falsy value in its place). Anyway:

036:0> meth
"nothing given"
037:0> meth(nil)
"we got nil"
038:0> meth('whatever')
"we got "whatever""
039:0> meth
"nothing given"

I can't vouch for JRuby or Ruby 1.9 or Rubinius or IronRuby for this
one (work's a strictly 1.8.6 kind of place).

-Chris
 
J

Joel VanderWerf

Matthew said:
As a followup, has_arguments? could be written as:

def has_arguments?(&b)
vars = eval("local_variables",b.binding)
return false if vars.length == 0
vars.each do |v|
return false if eval("#{v}.nil?",b)
end
return true
end
end


then you'd call it like:

if has_arguments? {} #note the empty block
something
end


Matt

Using #local_variables won't work for this, will it?

def has_arguments?(&b)
vars = eval("local_variables",b.binding)
return false if vars.length == 0
vars.each do |v|
return false if eval("#{v}.nil?",b)
end
return true
end

def foo(*a)
if has_arguments? {} #note the empty block
p a
else
puts "no args"
end
x=3 # <-- N.B.
end

foo()
foo(1,2,3)

__END__

Output:

no args
no args
 
M

Matthew K. Williams

Using #local_variables won't work for this, will it?

def has_arguments?(&b)
vars = eval("local_variables",b.binding)
return false if vars.length == 0
vars.each do |v|
return false if eval("#{v}.nil?",b)
end
return true
end

def foo(*a)
if has_arguments? {} #note the empty block
p a
else
puts "no args"
end
x=3 # <-- N.B.
end

foo()
foo(1,2,3)

__END__

Output:

no args
no args

Good point.... I didn't think of that case -- can you suggest a solution?
 
C

Charles Oliver Nutter

Messing with optional argument check for the umpteenth time, eg.

=C2=A0def meth(a=3DException)
=C2=A0 =C2=A0if a !=3D Exception
=C2=A0 =C2=A0 =C2=A0 ...
=C2=A0 =C2=A0else
=C2=A0 =C2=A0 =C2=A0...
=C2=A0 =C2=A0end
=C2=A0end

def meth(a=3D(defaults=3Dtrue;Exception))
if !defaults
...
else
...
end
end

- charlie
 
C

Charles Oliver Nutter

Here's a little trick I learned (very hacky):

=C2=A0def meth(a=3D(no_argument=3D'default'))
=C2=A0 =C2=A0if no_argument
=C2=A0 =C2=A0 =C2=A0'nothing given'
=C2=A0 =C2=A0else
=C2=A0 =C2=A0 =C2=A0"we got #{a.inspect}"
=C2=A0 =C2=A0end
=C2=A0end

Your default argument can't be falsy (but you could use something
truey and use your falsy value in its place). =C2=A0Anyway:

Yeah, my version avoids the falsy issue...but yours is shorter.

Either way...works fine in JRuby and 1.9.

- Charlie
 
B

Brian Candler

Thomas said:
So it occurs to me tonight that
we already have #has_block? to see if a block was passed. So how about
a #has_arguments? to query if _any_ arguments have been passed. So
then...

def meth(a=default)
if has_arguments?
...
else
...
end
end

Ah... now that would be nice.

But it doesn't solve the general case of

def meth(a, b, c=default)
...
end

You could have argument_size I suppose. I've never felt the need.

Generally the only time I've done this is with methods which have a hash
of options, in which case 'has_key?' does the job.
 
R

Robert Klemme

2009/8/19 Intransition said:
Messing with optional argument check for the umpteenth time, eg.

=A0def meth(a=3DException)
=A0 =A0if a !=3D Exception
=A0 =A0 =A0 ...
=A0 =A0else
=A0 =A0 =A0...
=A0 =A0end
=A0end

My 0.02 EUR: if your method's behavior changes depending on argument
or argument types chances are that you may rather want different
methods.

As a side note: I see this sometimes

def m(a =3D nil)
@a =3D a.nil? ? 123 : a
end

which is of course silly. The same would be much better expressed as

def m(a =3D 123)
@a =3D a
end

There is no need for additional optional logic - at least in these simple c=
ases.
Other's might do:

=A0def meth(*a)
=A0 =A0if !a.empty?
=A0 =A0 =A0a =3D a.first
=A0 =A0 =A0 ...
=A0 =A0else
=A0 =A0 =A0...
=A0 =A0end
=A0end

Neither of which are very satisfying. So it occurs to me tonight that
we already have #has_block? to see if a block was passed. So how about
a #has_arguments? to query if _any_ arguments have been passed. So
then...

=A0def meth(a=3Ddefault)
=A0 =A0if has_arguments?
=A0 =A0 =A0 ...
=A0 =A0else
=A0 =A0 =A0...
=A0 =A0end
=A0end

Ah... now that would be nice.

It may be useful in some cases but since you can always do the "trick"
with "*" I am not sure whether we really need this. I always found
block_given? a bit awkward since it looks like a method of self but is
rather a quick way to get at information about the argument list. The
only reason I use it at times is the fact that the combination m(&b)
b.call is slower than the combination m() yield.

I'm undecided really.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
B

Brian Candler

Robert said:
As a side note: I see this sometimes

def m(a = nil)
@a = a.nil? ? 123 : a
end

which is of course silly. The same would be much better expressed as

def m(a = 123)
@a = a
end

No, it's not the same. Consider:

def foo(a = nil)
m(a)
end

foo()

It saves replicating your default values all over the place, which isn't
very DRY.

Or you could more simply write

def m(a = nil)
@a = a || 123
end
 
R

Rick DeNatale

No, it's not the same. Consider:

=A0def foo(a =3D nil)
=A0 =A0m(a)
=A0end

=A0foo()

It saves replicating your default values all over the place, which isn't
very DRY.

I didn't take Robert as meaning that the above is silly.
Or you could more simply write

=A0def m(a =3D nil)
=A0 =A0@a =3D a || 123
=A0end

But, unless I'm missing something subtle, THIS is what Robert was
calling silly, since as far as I can tell, this

def m(a=3D123)
@a =3D a
end

Has exactly the same effect, and seems much clearer.

--=20
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
R

Robert Klemme

2009/8/20 Brian Candler said:
No, it's not the same.

Can you explain what the differences are?
Consider:

=A0def foo(a =3D nil)
=A0 =A0m(a)
=A0end

=A0foo()

I do not see the connection of this to what I posted.
It saves replicating your default values all over the place, which isn't
very DRY.

I don't see how this makes me replicate my default values all over the
place. My proposed solution just changes how a method internally
works, there is _no_ effect on the interface.
Or you could more simply write

=A0def m(a =3D nil)
=A0 =A0@a =3D a || 123
=A0end

I fail to see how this is simpler than

def m(a =3D 123)
@a =3D a
end

I'm in line with Rick: either _I_ must be missing something - or _you_. :)

Cheers

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
R

Robert Klemme

2009/8/20 Robert Klemme said:
Can you explain what the differences are?


I do not see the connection of this to what I posted.

Argh! Now I see what you mean. If you use nil as universal "absence"
marker then you have to declare the default only inside the method
when doing the a.nil? logic because you will pass it through from
calling methods.

I only considered a single method and did not have nested calls in
mind. Do you believe that these are common enough to warrant making
this a common pattern?

Btw, we can also do this:

def foo(*a)
bar(*a)
end

def bar(a =3D 123)
@a =3D a
end

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
B

Brian Candler

Rick said:
But, unless I'm missing something subtle, THIS is what Robert was
calling silly, since as far as I can tell, this

def m(a=123)
@a = a
end

Has exactly the same effect

I don't think so:

# prog1.rb
def m(a=123)
@a = a
end
def a
@a
end

def foo(x=nil)
m(x)
end
foo
puts "The result is #{a.inspect}" # shows nil

# prog2.rb
def m(a=nil)
@a = a || 123
end
def a
@a
end

def foo(x=nil)
m(x)
end
foo
puts "The result is #{a.inspect}" # shows 123
 
B

Brian Candler

Robert said:
I do not see the connection of this to what I posted.

What I mean is, when you have an argument with a default value, you may
want to use the default value even when nil is explicitly passed in,
e.g. m(nil)

This is usually because the function is being called from a higher-level
function, which passes through a value from a higher level again, and
doesn't want to be concerned with knowing what the default value is for
the low-level function. e.g.

# Option 1 - not very DRY

def z(a = 123)
puts "Handling #{a}"
end

def y(count = 3, a = 123)
count.times { z(a) }
end

def x(count = 3, a = 123)
y(count, a)
end

x()

# Option 2 - not always possible

def z(a = 123)
puts "Handling #{a}"
end

def y(count = 3, *args)
count.times { z(*args) }
end

def x(*args)
y(*args)
end

x()

# Option 3 - not pretty

def z(a = 123)
puts "Handling #{a}"
end

def y(count = 3, a = nil)
count.times { a ? z(a) : z }
end

def x(count = nil, a = nil)
count ? (a ? y(count,a) : y(count)) : y
end

x()

# Option 4

def z(a = nil)
a ||= 123
puts "Handling #{a}"
end

def y(count = nil, a = nil)
count ||= 3
count.times { z(a) }
end

def x(count = nil, a = nil)
y(count, a)
end

x()

This last option is the one which was called "silly", but I think in
this case it's actually quite a reasonable way to solve the problem. The
defaults are specified in exactly one place, and passing nil means 'use
the default'

Regards,

Brian.
 

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

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top