From: Daniel Berger Date: 2005-10-21T20:12:08+09:00 Subject: Re: A comparison by example of keyword argument styles Brian Mitchell wrote: > Hello fellow rubyists, > Sydney's argument scheme (s_ henceforth) is simple when you want to > reorder the arguments. > > def s_f1(a,b,c) ... end > s_f1(b:2, a:1, c:3) > > Matz's scheme (m_ from now on) allows this too: > > def m_f1(a:,b:,c:) ... end > m_f1(b:2, a:1, c:3) > > Ok. Not much difference right away no clear winner. Ruby has always favored implicitness over explicitness. The fact that I don't have to do anything explicitly in Sydney makes it the winner IMHO. > Lets examine > another variation on calling these: > > s_f1(1,2,3) # Simple. current behavior. > m_f1(1,2,3) # Error. No positional arguments. > > This shows one point that goes to s_. It makes it easy to use keyword > args that still have position. However, Matz could consider to allow > keyword arguments to count as positional arguments and make this > example go away. It is up to him. +1 for s_ for now. The change would > force non keyword args to come before keyword args. simple enough. > Though I still don't see a good reason to share both positional and > keyword arguments (a good example would be of great help in this > discussion). > > The next example will be a method that takes any number of key-worded arguments: > > def m_f2(**keys) ... end > m_f2(k1: 1, k2: 2, k3: 3) > > def s_f2(*args) ... end > s_f2(k1: 1, k2: 2, k3: 2) > > That works but there are some complications that the s_ method starts > to see (the hidden ugly head). *args now gets an array with hash > (key-value) or a value with each entry. Ugly. No, it's a single hash - [{:k1=>1, :k2=>2, :k3=>3}]. In Matz's implementation, there is no '*args' afaik, only '**keys', which is {:k1=>, :k2=>2, :k3=>3}. > Now something internal > is depending on how someone on the outside (away from the interface) > called it to see what it gets. I hope this is clear enough for you. +1 > for m_. I don't understand what you mean here. > How about mixing positional and keyword args? > > def m_f3(p1, p2, k1:, k2:) ... end > def s_f3(p1, p2, k1, k2) ... end > > *_f3(1,2,k1:3, k2: 4) > > Not much difference. m_ requires the extra : to be added. This is > neither a plus or a minus as it can be easily argued both ways. No > winner. (I will argue it if needed but trust me one can look both > ways). > > How about having a variable number of positional arguments and a set > number of keys? > > def m_f4(*args, a:, b:) > m_f4(1,2,3,4,5,6,7,8, a:1, b:2) # misleading see bellow. > > def s_f4(a, b, *args) > s_f4(1,2,3,4,5,6,7,8, a:1, b:2) # might have the same problem > > The s_ example is nice. It show an intuitive behavior at first but > depending on implementation you can no longer pull a variable number > of key paris or you have the same semantic problem that the m_ one > has. Huh? What makes you think the number of key pairs is limited? > If you use * at all that allows any number of arguments of any > type to be passed. Assuming the latter behavior (needed for *args to > work with delegation), then neither has any gain. I may be miss > understanding s_ at this point so please point it out. > > How about having both keyword and positional arguments mixed in a > catch-all with *? > > def m_f5(*args) > m_f5(1,2, a:3, b:4) > > def s_f5(*args) > s_f5(1,2 a:3, b:4) > > Well things start to contrast now. For s_ you get: [1,2, { :a => 3}, { > :b => 4}] if I understand correctly. m_ gives you [1,2, {:a => 3, :b > => 4}]. No. They are identical. > I won't debate on which is better in this case. Most of this > is involved with opinion. However, if you want to look at positionals > alone and keys alone it is easy with m_ we now have the hash collected > at the end of *args and can use **keys if we want to. Not a huge plus > but a point to make things easy. It will minimize boilerplate code on > a method. I give m_ a +1, you may disregard it if you don't agree. > > Now think about the above before we move on. Keep in mind that it is > not just another way to call a method but gives the method's interface > a richer meaning (like Matz's slide said). The ability to inspect positionals alone or keywords alone *will* be possible, via Behaviors. > Now for some more concrete examples of usage: > > Say we have an object that we create from a class through some special > method. The some arguments are required while others may or may not be > there but the circumstances differ. Imagine that the list of > attributes that can be passed may become quite long so using default > arguments wouldn't be a very good idea. Or even further, the keys > might be passed to a second function. This would normally be odd code > to see but it shows how the nature of the two methods differ by quite > a bit in real use. > > # Untested code. Could contain errors. At least I have an excuse this time. > class Pet > def self.m1_create(kind, name, **keys) > pet = Pet.allocate > pet.kind = kind > pet.name = name > case(kind) > when :ham > pet.weight = keys[:weight] > when :cat > pet.color = keys[:color] > when :dog > pet.color = keys[:color] > pet.breed = keys[:breed] > when :ruby > pet.facets = keys[:facets] > else > fail "Uknown kind of pet: #{kind}" > end > end > > # Same as m1_ but with a different method argument style. > def self.m2_create(kind:, name:, **keys) > # Lazy me ;) They are the same otherwise anyway. > m1_create(kind,name,**keys) > end > > def self.s_create(kind, name, *args) > pet = Pet.allocate > pet.kind = kind > pet.name = name > # Messy solution. There is probably a better one. > get = lambda {|sym| > args.find(lambda{{}}) {|e| > e.kind_of? Hash && e[sym] > }[sym] > } No, you would use Behaviors. This hasn't been entirely fleshed out yet, but it will likely look like this. def self.s_create(kind, name, *args) ... keys = KeywordBehavior.keyword_arguments ... end So, in that particular example, Sydney takes one more line. I think there will also be KeywordBehavior.positional_arguments and KeywordBehavior.arguments. The former would be positional arguments only, while the latter would be all arguments. Note that this provides equal or greater flexibility with regards to delegation, not less, as Matz suggested in one RedHanded comment. I've committed the test suite to the SVN repository if folks want to get a clearer idea of how things work (so far). It's "test_keyword_arguments.rb" under test/ruby. Regards, Dan