Forum: Ruby Re: Confusion Over Keyword Arguments

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Berger, Daniel (Guest)
on 2006-03-02 17:57
(Received via mailing list)
> -----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

<snip>

> |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:

http://djberg96.livejournal.com/51344.html
http://djberg96.livejournal.com/51052.html
http://djberg96.livejournal.com/50325.html
http://djberg96.livejournal.com/50162.html

Regards,

Dan
unknown (Guest)
on 2006-03-02 18:05
(Received via mailing list)
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
James G. (Guest)
on 2006-03-02 18:10
(Received via mailing list)
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
unknown (Guest)
on 2006-03-02 18:19
(Received via mailing list)
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 :-)
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 :-)
(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
Jeff C. (Guest)
on 2006-03-02 18:33
David Black wrote:
> Then again, I'm not sure about keyword arguments, so I don't count :-)
> (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 :-)

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

Jeff
www.softiesonrails.com
Yukihiro M. (Guest)
on 2006-03-02 18:35
(Received via mailing list)
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.
Srinivas J. (Guest)
on 2006-03-02 19:13
(Received via mailing list)
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
Daniel B. (Guest)
on 2006-03-02 19:31
(Received via mailing list)
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/namedParameters...
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
Yukihiro M. (Guest)
on 2006-03-02 19:51
(Received via mailing list)
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.
Daniel B. (Guest)
on 2006-03-02 20:11
(Received via mailing list)
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
E. Saynatkari (Guest)
on 2006-03-02 22:53
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
E. Saynatkari (Guest)
on 2006-03-02 23:21
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/namedParameters...
> 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
This topic is locked and can not be replied to.