Re: Confusion Over Keyword Arguments


#1

-----Original Message-----
From: Yukihiro M. [mailto:removed_email_address@domain.invalid]
Sent: Wednesday, March 01, 2006 4:46 PM
To: ruby-talk ML
Subject: Re: Confusion Over Keyword Arguments

|How about “=” for keyword arguments instead
|(such as in python)?

Unfortunately, assignments are legal in argument list in Ruby.

  					matz.

That can be made to work, with the understanding that ‘=’ in a method
call means ‘keyword argument’, not ‘assignment’, since there is no point
in doing an assignment in a method call.

Method definition, ‘=’ means assignment (of default value):

def foo(bar, baz = 3)

end

Method call, ‘=’ means keyword

foo(baz = 5, bar = 2)

For anyone who cares to read my past thoughts on the subject:




Regards,

Dan


#2

On Fri, 3 Mar 2006, Berger, Daniel wrote:


end

Method call, ‘=’ means keyword

foo(baz = 5, bar = 2)

but is

foo baz = 5, bar = 2

a, b = foo(baz = 5), (bar = 2)

or

a = foo( (baz = 5), (bar = 2) )

??

-a


#3

On Mar 2, 2006, at 9:57 AM, Berger, Daniel wrote:

That can be made to work, with the understanding that ‘=’ in a method
call means ‘keyword argument’, not ‘assignment’, since there is no
point
in doing an assignment in a method call.

You could use it to avoid repeating myself, for example in testing:

assert_instance_of(Name, (name = Name.new( (first = “James”), (last =
“Gray”) )))
assert_equal(first, name.first)
assert_equal(last, name.last)

James Edward G. II


#4

Hi –

On Fri, 3 Mar 2006, removed_email_address@domain.invalid wrote:

|How about “=” for keyword arguments instead

Method definition, ‘=’ means assignment (of default value):

a, b = foo(baz = 5), (bar = 2)

or

a = foo( (baz = 5), (bar = 2) )

You may have to bite the bullet and dust off your parentheses keys :slight_smile:
I’ve never been a big fan of things like:

def meth a, b, c = 1

anyway. I don’t think mandatory parens would be so bad… though I
know that’s not a unanimous opinion.

I’m not sure about ‘=’ in method calls for keyword arguments, though.
Then again, I’m not sure about keyword arguments, so I don’t count :slight_smile:
(My concern is that I distrust anything that couples local variables
in one scope with, essentially, anything in any other scope.)

David


David A. Black (removed_email_address@domain.invalid)
Ruby Power and Light (http://www.rubypowerandlight.com)

“Ruby for Rails” chapters now available
from Manning Early Access Program! http://www.manning.com/books/black


#5

David Black wrote:

Then again, I’m not sure about keyword arguments, so I don’t count :slight_smile:
(My concern is that I distrust anything that couples local variables
in one scope with, essentially, anything in any other scope.)

I completely agree. I kind of like the current technique of using an
“implicit” hash to pass named values.

I think the main attraction of keyword arguments is to let the caller
pass arguments in any order they want as long as they use the names.

But if I have so many arguments that it’s hard for the client to
remember the order, then the arguments should probably be refactored
into a structure anyway (like a hash :slight_smile:

So to me keyword arguments are at best syntactic sugar and at worst a
way to cover-up code smells.

Jeff
www.softiesonrails.com


#6

Hi,

In message “Re: Confusion Over Keyword Arguments”
on Fri, 3 Mar 2006 00:57:44 +0900, “Berger, Daniel”
removed_email_address@domain.invalid writes:

|That can be made to work, with the understanding that ‘=’ in a method
|call means ‘keyword argument’, not ‘assignment’, since there is no point
|in doing an assignment in a method call.

I’m not sure if we can. yacc is a tough guy to fight with.

|http://djberg96.livejournal.com/50162.html

In this article, you’ve proposed *rest to slurp keyword hash. Indeed
it is simple, but maybe too simpler. I don’t get how it can co-exist
with your ideal keyword parameters. I.e. how the following code
should work?

def foo(a, b=0, *c)

end

foo(1, foo:3) # (a=1, b={:foo=>3}) or (a=1,b=0,c=[{:foo=>3}])?
foo(1, 2, 8, c:5) # c={:c=>5}) or c=[8,{:c=>5}] or error?
args = [1, {:b=>2, foo=>5}]
foo(*args) # (a=1,b={:b=>2, foo=>5}) or (a=1,b=2,c=[{:foo=>5}])?

						matz.

#7

This has reference to the discussion in the article below.

http://djberg96.livejournal.com/51052.html

From the discussion on parameters to Array.new therein, I got the
feeling that there were two distinct issues that somehow got mixed up:

(a) method signature – how many arguments does it accept, which have
defaults, etc., and

(b) which set of arguments belong to the same signature.

Just because of Array.new has a form that accepts size and default
object, and another that accepts an array, it does not directly lead
to a capability to mix those arguments into the same call.

Even today, there are sanity checks to prevent such things as

 Array.new(4, {}) { | i | i * i },

and such shall always be needed. And I feel that the warning that Ruby
generates on evaluating the above, is very correct.

Am I seriously missing something in Daniel’s argument?

Best regards,

JS


#8

Hi,

In message “Re: Confusion Over Keyword Arguments”
on Fri, 3 Mar 2006 02:30:34 +0900, Daniel B.
removed_email_address@domain.invalid writes:

|> foo(1, foo:3) # (a=1, b={:foo=>3}) or (a=1,b=0,c=[{:foo=>3}])?
|
|Error. There’s no ‘foo’ parameter. Passing a literal hash would require {}.

I’m not sure what you meant by “literal hash”. Do you mean foo:3 is
not legal since you don’t have an argument with a name “foo”?

Have you changed your mind since the blog entry at
http://djberg96.livejournal.com/50162.html? For you have written

foo(1, 2, foo:4, bar:5) # a=1, b=2, c=[{‘foo’=>4, ‘bar’=>5}]
foo(1, 2, 8, baz:5) # a=1, b=2, c=[8, {‘baz’=>5}]

in it. foo:4 etc. seems like non-literal hash.

|> args = [1, {:b=>2, foo=>5}]
|> foo(*args) # (a=1,b={:b=>2, foo=>5}) or (a=1,b=2,c=[{:foo=>5}])?
|
|a = 1, b = {:b=>2, foo=>5}, since you’ve passed a literal hash.

This means that you cannot accept

(a) a keyword argument that is possibly accepted by the superclass’s
method, without knowing their names
(b) arbitrary keywords that can be passed to other methods

Right? If so, (a) means you need to have exact argument information
about a superclass method, which may not be available for mix-in
modules; (b) means there’s no way to convert keyword arguments to a
hash.

Am I missing something?

						matz.

#9

Yukihiro M. wrote:

not legal since you don’t have an argument with a name “foo”?

Have you changed your mind since the blog entry at
http://djberg96.livejournal.com/50162.html? For you have written

foo(1, 2, foo:4, bar:5) # a=1, b=2, c=[{‘foo’=>4, ‘bar’=>5}]
foo(1, 2, 8, baz:5) # a=1, b=2, c=[8, {‘baz’=>5}]

in it. foo:4 etc. seems like non-literal hash.

Oops - I didn’t notice you had declared it “*c” instead of “c”. My blog
entry
is the expected behavior.

|> args = [1, {:b=>2, foo=>5}]
|> foo(*args) # (a=1,b={:b=>2, foo=>5}) or (a=1,b=2,c=[{:foo=>5}])?
|
|a = 1, b = {:b=>2, foo=>5}, since you’ve passed a literal hash.

This means that you cannot accept

(a) a keyword argument that is possibly accepted by the superclass’s
method, without knowing their names

I’m not sure I follow. How would you know the keyword arguments without
knowing their names? Or are we getting into keyword discovery here?

(b) arbitrary keywords that can be passed to other methods

What do you mean by ‘arbitrary keywords’?

Right? If so, (a) means you need to have exact argument information
about a superclass method, which may not be available for mix-in
modules;

I’m afraid I’m not following.

(b) means there’s no way to convert keyword arguments to a

hash.

Are you talking internals, or by the user?

Well, anyway, I think this has all been gone over before. I probably
should
have just kept my mouth shut.

Dan


#10

Yukihiro M. wrote:

foo(1, foo:3) # (a=1, b={:foo=>3}) or (a=1,b=0,c=[{:foo=>3}])?

Error. There’s no ‘foo’ parameter. Passing a literal hash would
require {}.

foo(1, 2, 8, c:5) # c={:c=>5}) or c=[8,{:c=>5}] or error?

Hm. Either an error or c = [5], depending on whether or not your want
to
declare that, once a positional is used, there’s no going back after the
fact.

args = [1, {:b=>2, foo=>5}]
foo(*args) # (a=1,b={:b=>2, foo=>5}) or (a=1,b=2,c=[{:foo=>5}])?

a = 1, b = {:b=>2, foo=>5}, since you’ve passed a literal hash.

Here’s the latest test case we had for Sydney. Note that Sydney assumes
that
all method arguments automatically become keyword arguments. There’s no
syntax
for explicitly declaring that a given method parameter is a valid
keyword argument.

For the kids following along at home, also see
http://redhanded.hobix.com/inspect/namedParametersArenTTheyAllNamed.html
for
further discussion on the subject.

Regards,

Dan

Tests the keyword argument behavior.

Authored by Dan B. and Evan Webb

require ‘behavior/keyword’
require ‘test/unit’

This is the class we use within the test case below.

class Foo
MYCONST = 5
behavior KeywordBehavior

attr_reader :x, :y, :z
def bar(x, y, z=3)
   @x = x
   @y = y
   @z = z
end

def grab(x, y, *z)
   @x = x
   @y = y
   @z = z
end

def vars
   [@x, @y, @z]
end

def t2(x)
   name = "blah"
end

end

Added to test a method redefinition in a subclass, as well as the

define_method and instance_method methods.

class Bar < Foo
attr_reader :a, :b, :c
def bar(a, b=2, c=3, d=4)
@a = a
@b = b
@c = c
end
define_method(:baz, instance_method(:bar))
end

TOPLEVEL=5

class TC_Arguments < Test::Unit::TestCase
def setup
@foo = Foo.new
@bar = Bar.new
@arr = [1,2,3]
end

def test_methods_defined
   assert_respond_to(@foo, :bar)
   assert_respond_to(@bar, :bar)
   assert_respond_to(@foo, :grab)
   assert_respond_to(@foo, :vars)
   assert_respond_to(@foo, :t2)
   assert_respond_to(@bar, :baz)
end

# Methods may still take positional arguments, the way they always 

have
def test_positional_basic
assert_nothing_raised{ @foo.bar(1,2) }
assert_nothing_raised{ @foo.bar(1,2,3) }
end

def test_positional_basic_subclass
   assert_nothing_raised{ @bar.bar(1) }
   assert_nothing_raised{ @bar.bar(1,2) }
   assert_nothing_raised{ @bar.bar(1,2,3) }
   assert_nothing_raised{ @bar.bar(1,2,3,4) }
   assert_nothing_raised{ @bar.baz(1,2,3) }
end

# You can use named parameters, using the name of the parameter 

defined in
# in the method itself. It must be the name of the parameter,
followed by
# a colon, followed by the value. Spaces between the parameter
name, colon
# and value are not allowed.
def test_named_basic
assert_nothing_raised{ @foo.bar(z:1, x:2, y:3) }
assert_equal(2, @foo.x)
assert_equal(3, @foo.y)
assert_equal(1, @foo.z)
end

def test_named_basic_subclass
   assert_nothing_raised{ @bar.bar(d:1, a:2, b:3, c:4) }
   assert_equal(2, @bar.a)
   assert_equal(3, @bar.b)
   assert_equal(4, @bar.c)
   assert_equal(1, @bar.d)
end

# Ensure that named parameters in a define_method/instance_method 

work.
# In this case, Bar#baz is actually calling Foo#bar.
def test_named_dynamically_defined
assert_nothing_raised{ @bar.baz(z:3, y:2, x:1) }
assert_equal(1, @bar.x)
assert_equal(2, @bar.y)
assert_equal(3, @bar.z)
end

# Default values in a declaration work the same way they always 

have. The
# use of named parameters does not change this.
def test_named_basic_default_values
assert_nothing_raised{ @foo.bar(x:1, y:2) }
assert_equal(1, @foo.x)
assert_equal(2, @foo.y)
assert_equal(3, @foo.z) # default
end

# You can mix and match positional and named parameters, though 

there are
# limitations. Positional parameters must come first (i.e. on the
left
# side).
def test_mixed_basic
assert_nothing_raised{ @foo.bar(1, z:2, y:3) }
assert_equal(1, @foo.x)
assert_equal(3, @foo.y)
assert_equal(2, @foo.z)

   assert_nothing_raised{ @foo.bar(1, 2, z:4) }
   assert_equal(1, @foo.x)
   assert_equal(2, @foo.y)
   assert_equal(4, @foo.z)
end

# You can splat an array, and the values will be assigned in a left 

to right
# fashion, as in the current Ruby behavior.
def test_splat
assert_nothing_raised{ @foo.bar(*@arr) }
assert_equal(1, @foo.x)
assert_equal(2, @foo.y)
assert_equal(3, @foo.z)
end

# You can mix and match positional, named and splat arguments. 

However,
# splat arguments must come last. This is the same as the current
behavior
# in Ruby.
def test_mixed_splat
a = [1]
assert_nothing_raised{ @foo.bar(*a) }
assert_nothing_raised{ @foo.bar(1, *a) }
assert_nothing_raised{ @foo.bar(1, y:2, *a)}
assert_nothing_raised{ @foo.bar(1, 2, *a) }
assert_nothing_raised{ @foo.bar(x:1, y:2, *a) }
end

# The '*rest' declarations work the same as they always have.  In 

the case
# of named parameters, the values assigned to the ‘rest’ argument
are
# simply pushed as an array.
def test_def_splat
assert_nothing_raised{ @foo.grab(1, 2, 3, 4) }
assert_equal([1, 2, [3, 4]], @foo.vars)

   assert_nothing_raised{ @foo.grab(x:1, y:2, z:4) }
   assert_equal([1, 2, [4]], @foo.vars)

   assert_nothing_raised{ @foo.grab(1, 2, 3, [1,2,3])
   assert_equal([1,2,3,[[1,2,3]]], @foo.vars)

   assert_nothing_raised{ @foo.grab(1, 2, 3, z:[1,2,3])
   assert_equal([1,2,3,[[1,2,3]]], @foo.vars)

   assert_nothing_raised{ @foo.grab(1, 2, 3, z:*[1,2,3])
   assert_equal([1,2,3,[1,2,3]], @foo.vars)
end

# If a keyword argument is assigned to a '*rest' parameter, then it
# autovivifies a hash, assigning that hash as the argument to the
# '*rest' parameter.  Note double declarations are still illegal.
def test_keyword_in_splat
   assert_nothing_raised{ @foo.grab(10, 20, name:"evan", age:99) }
   assert_equal([ 10, 20, [{:name => "evan", :age=>99}] ], 

@foo.vars)
end

# If nothing is assigned to the '*rest' variable, then it is simply 

empty.
# is the same as the current Ruby behavior.
def test_keyword_no_splat
@foo.grab(x:1, y:2)
assert_equal [1,2,[]], @foo.vars
end

# The presence of a '*rest' declaration does not alter required 

arguments.
def test_keyword_splat_with_not_enough
assert_raises(ArgumentError) { @foo.grab(x:1) }
end

# Ensure that the presence of a parameter in one method declaration 

does
# not interfere with an identically named parameter in another
declaration.
def test_no_local_leakage
assert_nothing_raised { @foo.t2 x:8 }
end

# Ensure that "::" doesn't cause problems
def test_works_with_constants
   assert_nothing_raised{ @foo.bar(y:1, x:2, z:Foo::MYCONST) }
   assert_nothing_raised{ @foo.bar(y: 1, x: 2, z: ::TOPLEVEL) }
end

def test_expected_argument_errors
   assert_raises(ArgumentError){ @foo.bar }  # x & y missing
   assert_raises(ArgumentError){ @foo.bar(1) }           # y missing
   assert_raises(ArgumentError){ @foo.bar(x:1) }         # y missing
   assert_raises(ArgumentError){ @foo.bar(y:1) }         # x missing
   assert_raises(ArgumentError){ @foo.bar(x:1, z:2) }    # y missing
   assert_raises(ArgumentError){ @foo.bar([1,2,3]) }     # y missing
   assert_raises(ArgumentError){ @foo.bar(*[1]) }        # y missing
   assert_raises(ArgumentError){ @foo.bar(x:1,y:1,a:3) } # no 'a' 

parameter

   assert_raises(ArgumentError){ @foo.bar(*[1,2,3,4]) }  # too many 

args
end

# Ensure that the subclass didn't pick up the named parameters
# from the parent
def test_expected_argument_errors_subclass
   assert_raises(ArgumentError){ @bar.bar }
   assert_raises(ArgumentError){ @bar.bar(x:1) }
   assert_raises(ArgumentError){ @bar.bar(y:1) }
   assert_raises(ArgumentError){ @bar.bar(z:1) }
end

def test_expected_syntax_errors
   assert_raises(SyntaxError){ @foo.bar(x:1, 2) }  # positional must 

be first
assert_raises(SyntaxError){ @foo.bar(*a, 1) } # splat must be
last
assert_raises(SyntaxError){ @foo.bar(*a, x:1) } # splat must be
last
end

def teardown
   @foo = nil
   @arr = nil
end

end


#11

Daniel B. wrote:

Yukihiro M. wrote:

foo(1, foo:3) # (a=1, b={:foo=>3}) or (a=1,b=0,c=[{:foo=>3}])?

Error. There’s no ‘foo’ parameter. Passing a literal hash would
require {}.

foo(1, 2, 8, c:5) # c={:c=>5}) or c=[8,{:c=>5}] or error?

Hm. Either an error or c = [5], depending on whether or not your want
to
declare that, once a positional is used, there’s no going back after the
fact.

args = [1, {:b=>2, foo=>5}]
foo(*args) # (a=1,b={:b=>2, foo=>5}) or (a=1,b=2,c=[{:foo=>5}])?

a = 1, b = {:b=>2, foo=>5}, since you’ve passed a literal hash.

Here’s the latest test case we had for Sydney. Note that Sydney assumes
that
all method arguments automatically become keyword arguments. There’s no
syntax
for explicitly declaring that a given method parameter is a valid
keyword argument.

For the kids following along at home, also see
http://redhanded.hobix.com/inspect/namedParametersArenTTheyAllNamed.html
for
further discussion on the subject.

Regards,

Dan

Slightly off-topic but I found this part curious:

You can use named parameters, using the name of the parameter

defined in

in the method itself. It must be the name of the parameter,

followed by

a colon, followed by the value. Spaces between the parameter

name, colon

and value are not allowed.

Why is this? foo: bar would seem to be more legible with the
added benefit of no confusion with the scope operator :: nor
Symbols.

Your testcases do not seem to cover the scope completely and
Symbols are not included at all. Maybe add something like

def test_corner_cases()
# Symbols
assert_nothing_raised {@foo.bar x:1, y:2, z::symbol}

# Scopes
assert_nothing_raised {@foo.bar A::B, y:2, z:3}
assert_nothing_raised {@foo.bar x:1, y:2, z:::Object}

assert_nothing_raised{ @foo.bar(z:1, x:2, y:3) }
end # test_corner_cases

To cover those.

< snip rest of test cases />

E


#12

Daniel B. wrote:

Yukihiro M. wrote:

not legal since you don’t have an argument with a name “foo”?

Have you changed your mind since the blog entry at
http://djberg96.livejournal.com/50162.html? For you have written

foo(1, 2, foo:4, bar:5) # a=1, b=2, c=[{‘foo’=>4, ‘bar’=>5}]
foo(1, 2, 8, baz:5) # a=1, b=2, c=[8, {‘baz’=>5}]

in it. foo:4 etc. seems like non-literal hash.

Oops - I didn’t notice you had declared it “*c” instead of “c”. My blog
entry
is the expected behavior.

|> args = [1, {:b=>2, foo=>5}]
|> foo(*args) # (a=1,b={:b=>2, foo=>5}) or (a=1,b=2,c=[{:foo=>5}])?
|
|a = 1, b = {:b=>2, foo=>5}, since you’ve passed a literal hash.

This means that you cannot accept

(a) a keyword argument that is possibly accepted by the superclass’s
method, without knowing their names

I’m not sure I follow. How would you know the keyword arguments without
knowing their names? Or are we getting into keyword discovery here?

(b) arbitrary keywords that can be passed to other methods

What do you mean by ‘arbitrary keywords’?

I presume both this and the section before, Matz means
a situation like this:

def actual_method(a, b, c, d)
# …
end

def delegator(*args, &block)
actual_method *args, &block
end

delegator 1, 2, 3, d:4

The call is not really possible unless the keyword
‘d’ is automatically converted to a positional or
passed through in a hash or something like that.

The simple answer is that it is the responsibility
of the implementer of #delegator to map the call
so that no error is raised. This may not work in
cases where the whole of the dispatch is done
dynamically (although these may be quite rare).

Right? If so, (a) means you need to have exact argument information
about a superclass method, which may not be available for mix-in
modules;

I’m afraid I’m not following.

(b) means there’s no way to convert keyword arguments to a

hash.

Are you talking internals, or by the user?

Well, anyway, I think this has all been gone over before. I probably
should
have just kept my mouth shut.

Dan

E