hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Named Parameters? Aren't They All Named? #

by why in inspect

None of us wants to delve back into the block/method/param argument again, right? But we could still use a good answer. Dan Berger has an explanation of how Sydney got named parameters without need to plunder Ruby’s current method definitions.

 # A standard Ruby method declaration
 def foo(a, b, c=4)
   ...
 end

 # Ideal syntax
 foo(a:1, b:2, c:3) # a=1, b=2, c=3
 foo(a:1, b:2)      # a=1, b=2, c=4
 foo(1, 2, c:3)     # a=1, b=2, c=3
 foo(1, 2, 3)       # a=1, b=2, c=3

If you’d like to give Sydney a try, scan for tarballs on Evan’s blog. (Spotted on Late to the Party.)

said on 18 Oct 2005 at 16:35

That’s exactly what I thought. I was going to ask but I figured someone would just tell me to read ruby-talk.

said on 18 Oct 2005 at 17:01

eww. Someone tell Perl Ruby’s catching up!

said on 18 Oct 2005 at 17:06
Interesting. The following is conspicuously absent from the examples; is it allowed?

foo(b:2, a:1) # a=1, b=2, c=4

And do all of these work?

foo(1, module_var::ConstantName)

foo(1, b: :two)

foo(1, ::ConstantName)

foo(1, 2, b: :two)

foo(1, b: x ? 2 : :two)

said on 18 Oct 2005 at 20:23

Dave, yes, you could do foo(b:2, a:1) and it would work.

Your second case, foo(1, mod::Const), would work like a regular positional parameter, but that’s a good test case to add. Same for foo(1, ::Const) example. Again a good test case.

The foo(1, 2, b: :two) example would fail. There cannot be a space between the keyword, the colon and the value. Hm…I wonder how it should handle symbols then. That will look strange.

I suspect the last example will fail, though I’ve never used conditional logic like that for a parameter. I’ll ask Evan. :)

said on 18 Oct 2005 at 20:35

Hmm, that’s an explanation of why Sydney got keyword parameters, but not how it was done. I do really like the syntax though.

If you want to use the variable names, use them, if you don’t, don’t. There are no special keyword parameters.

said on 18 Oct 2005 at 21:15

Two issues:

  • Ruby C API does not have any way to provide name information for methods defined in C. I’m afraid that making ordinary arguments as named as well would cause serious breakage of extensions and built-in methods.
  • many positional arguments are not named properly, since they have been a part of implementation, not public interface. For the same reason, argument names varies among overriding methods, that may confuse the users.

Proposed keyword arguments are not that complex once you understands the whole picture.

- matz.

said on 19 Oct 2005 at 00:00

Matz, as to your first point, I’ll have to talk to Evan about that. If it takes extra work to create named parameters in C extensions, then so be it. Something like a “define_named_parameters()” function perhaps, but whatever. The onus will be on the C extension developers.

Regarding the second point, I’m not sure where you’re going. If the interface isn’t public it’s a non issue because no one is going to use it anyway. As for proper names, well, I think being forced to use proper names is a good thing.

Now, when I say that, I have the stdlib and 3rd party stuff in mind, not the core. One potential option to avoid the “keywords run amok” scenario that you envision is to make keyword arguments something that must be explicitly enabled via Evan’s “Behaviors” implementation.

In fact, that’s how it works now, but Evan was going to make that the default behavior.

said on 19 Oct 2005 at 00:10

Dave, correction, a space is allowed after the colon. So, your symbol example should work. I suspect the ternary operation might require an extra set of parens, but we’ll see.

said on 19 Oct 2005 at 01:24

Daniel, matz’s second point wasn’t about the entire method not being public, but the fact that the actual names of the arguments are not part of the interface of a public method. They are implementation details that are hidden from the user, and rightly so (they are basically local variables to the method. The user shouldn’t need to know about their names).

When one uses keyword arguments, one is basically breaking the premise that implementation details are hidden. Suddenly implementation details (the names of the arguments) become part of the interface. So it makes sense that this case should be the marked one, thus that keywords arguments should explicitly be marked that way in the definition of the method, to insist that they are suddenly part of its public interface.

said on 19 Oct 2005 at 06:34

All arguments could be returned in a single ordered hash/array:


foo( 1, 2, key1: 3, key2: 4 )

{ 0 => 1,
  1 => 2,
key1 => 3,
key2 => 4 }

said on 19 Oct 2005 at 07:42

Patrick, it is possible, but I’m afraid it slows down (already slow) method invocation even worse.

said on 19 Oct 2005 at 09:12

Patrick May: Did I ever mention that I’m still envious of Lua’s tables, and want them in Ruby? (grin)

Daniel Berger: Could we have named params without default values, like: def foo(bar:, baz:); do_stuff_with(bar,baz); end?

(I don’t understand the colo(u)r rules for paragraphs :), off-topic-ly.)

said on 19 Oct 2005 at 09:55

hgs, I’m not sure without asking. I’m also not sure what you want to happen in that case. Seems a pointless thing to do.

said on 19 Oct 2005 at 11:15

Matz: Fair enough. What I’m curious about is, what is necessary to delegate a method?

currently:

def method_missing( symbol, *args, &block )
  delegate.send( symbol, *args, &block )
end

future?

def method_missing( symbol, *rest, **keys, &block )
  @delegate.send( symbol, *rest, **keys, &block )
end

said on 19 Oct 2005 at 11:17

hgs: Lua looks interesting, I’m checking it out.

said on 19 Oct 2005 at 12:18

Daniel Berger: def foo(a:, b:)... (with more params) would allow people who wished to use the names to order the parameters differently. I’ve been using Ruby since about 2000 but I’ve always had to lookup which way round alias works. I’ve got it now, but to be able to do alias new: :bar, old: :foo would be less confusing for me because I always want to read it as “alias that to this_new_name”, which is actually backwards.

said on 19 Oct 2005 at 13:05

hgs, oh, I think I misread your post as a method call, not a declaration. Like I said, you won’t have to do anything special in the method declaration. Thus “def foo(a, b)” can be called as “foo(b:1, a:2)” if you want.

And yes, with the current Sydney syntax, you can do exactly what you want wrt alias. :)

said on 19 Oct 2005 at 16:24

@Patrick, @matz: I also thought about delegation of all parameters, whether normal, multiple, named or block. It would get really verbose if you’d have to give four different variables just for delegating; and it’s bad for performance either, I think.

Ruby already has a cool syntax for “any parameters allowed”, it’s def foo(*).

So what about making this available for method calls, too:

def foo(*)
  bar(*)
end

Is that possible? Or is there another solution?

said on 19 Oct 2005 at 17:55

Since all keyword arguments will be packed in a hash at the end of ordinal argument list, delegation should be done by plain

  def foo(*args)
    bar(*args)
  end

even after introducing keyword arguments. That’s one of the design goal of my keyword argument design.

said on 19 Oct 2005 at 18:45

I don’t mind Matz’s proposal. In fact I like it a lot. You guys don’t give anything a try it seems. There are plenty of good points about it:

  • It isn’t too hard to use. If it is there are plenty of other things in Ruby that you can complain about
  • It works with C extensions
  • It works with catch alls. Clean and easy conditional delegation. How would *args work with simple named params?
  • It doesn’t break anything nor does the other proposal.
  • It allows some easier specialization of an interface. Think of having a strict number of positional arguments and a variable number of named arguments. It just doesn’t work without splitting them up. This is powerful and aids Ruby in providing small, compact, but still flexible and capable interfaces.

I think pushing for the simple Python style model will be passing up an oportunity to allow for things that can’t be done otherwise. Play around with some psuedo code and see what I mean.

You guys are all whining and it is not that big of a deal. Give Matz a break at least…

said on 19 Oct 2005 at 20:29

Matz, then todays hash elements would be index -2? Or are named arguments and ending hash parameters mixed?

def foo(*args)
  p args
end
foo( :b=>2, a:1 )
=> [ {:b=>2}, {:a=>1} ]

or

=> [ {:b=>2, :a=>1} ]
said on 19 Oct 2005 at 20:58

Mixing named arguments and ending hash parameters might make sense. For instance, if Rails switched from using hashes to named parameters, it would be rather painless if the two were the same.

said on 19 Oct 2005 at 23:52

Trans, you can’t mix in-line hash and keyword arguments, so that

foo(:b=>2,a:1)

would cause an error. But both

foo(b:2,a:1)

and

foo(:b=>2,:a=>1)

works, and args becomes [{:a=>1,:b=>2}]

said on 20 Oct 2005 at 06:38

Matz: Thanks for the answers! I think I understand… :-)

said on 20 Oct 2005 at 11:41

I really like the new proposal, I always thought splitting named arguments from positional ones was ugly. Even if I still think **rest arguments are more clean than catch-all *rest. But should’nt a debate like this happen on rcrchive?

said on 20 Oct 2005 at 18:54
I’m confused by the delegation statements made by Matz and **keys. Say you have this snippet of code:
class Foo
   def foo(*args)
      bar(*args)
   end

   def bar(x:,y:,z:)
      p x
      p y
      p z
   end
end

f = Foo.new
f.foo(a:1, b:2, c:3)
What happens here? Is this an ArgumentError? Or are the arguments passed positionally, left to right?

If the former, what’s special about the delegation abilities you mention? If the latter, how do you get around using an ordered hash?

said on 20 Oct 2005 at 20:56

I don’t like the simplified version. It seems to me that you are taking positional arguments and making them unpositional with some naming syntax sugar.

With Matz’s proposal we can treat named arguments like attributes of a call. I think the difference in potency is like night and day.

In the end I would rather not have named arguments if we don’t have something equivalent to Matz’s proposal. It just adds too much with little gain. I should make it more clear: Matz’s proposal and this one are not equivalent in fact they are quite different.

Maybe we can spend all of this energy trying to make syntax more intuitive than trying to accomplish something completely different.

said on 20 Oct 2005 at 22:48

I have absolutely no idea what you’re going on about Brian M. The only external differences between the Rite and Sydney implementations are in explicit declaration vs. implicit and the way *rest arguments are handled.

It seems to me that you are taking positional arguments and making them unpositional with some naming syntax sugar.

That’s true of any named argument scheme in existence.

With Matz’s proposal we can treat named arguments like attributes of a call. I think the difference in potency is like night and day.

Care to elaborate? I don’t follow.

It just adds too much with little gain.

So, somehow, Matz’s proposal, which requires more syntax, is not “too much”?

Maybe we can spend all of this energy trying to make syntax more intuitive than trying to accomplish something completely different.

I’m not sure how you get more intuitive than foo(x:1, y:2), unless you want something like foo(:x=>1, :y=>2), which can also be done.

said on 21 Oct 2005 at 00:13

Daniel, take time to think about my slides please. Although it has more syntax, it’s much simpler in reality.

class Foo
   def foo(*args)
      bar(*args)
   end

   def bar(x:,y:,z:)
      p x
      p y
      p z
   end
end

f = Foo.new
f.foo(a:1, b:2, c:3)

Foo#foo receives [{:a=>1, :b=>2, :c=>3}], since all keyword arguments are packed in a hash at the the last argument, thus it is contained in the rest argument (args this case). Baceuse Foo#bar takes three non-optional keyword arguments x, y and z, and arguments passed from foo (args) does not have either of them, so that Foo#bar causes ArgumentError.

said on 21 Oct 2005 at 00:33

I started writing a response but with the examples it became very long. I will be posting it to the ruby-talk ML shortly.

said on 24 Oct 2005 at 19:44

For symbols, couldn’t you just use this?

foo(1, b:(:two))

said on 25 Oct 2005 at 02:42

Matz’s last example can be extended by adding: bar(args.sort.collect {|i| i + 1}. *args.sort may (also) become useful if you want to use unpositional constructs like: f.foo(“c:” << 1.to_s, ...; you may cut off “c:” again in the collect block). – For a different kind of hash, google for “ruby, pseudohash” (or use associative arrays: a = [[1,2,3], [4,5,6]]; p a[ 0].type in combination with hashes)!

Comments are closed for this entry.