A comparison by example of keyword argument styles

B

Brian Mitchell

Hello fellow rubyists,

What I have bellow is what started as a post to RedHanded. It was
growing in size too rapidly so I decided to post here for all to see.
Sorry for starting yet another thread on these topics. It is rough so
please don't nit pick details. I don't want this to start a flame war
(though I can't do much about that now). I would rather see some ideas
on how to get the best of both worlds. Some of this won't come without
a compromise so keep that in mind. I apologize in advance if I did
make any grievous errors in my interpretations.

There is a matter of taste involved but beyond that there are a few
easy comparisons. I will try to keep this down to just that (though a
few may be on a grey line, I hope they are clear enough).

Let me cite Matz's slides first:
* Make method calls more descriptive
* Order free arguments

With that simple goal in mind, lets start the comparisons.

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. 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 argum=
ents:

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. 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_.

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. 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 =3D> 3}, {
:b =3D> 4}] if I understand correctly. m_ gives you [1,2, {:a =3D> 3, :b
=3D> 4}]. 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).

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 =3D Pet.allocate
pet.kind =3D kind
pet.name =3D name
case(kind)
when :ham
pet.weight =3D keys[:weight]
when :cat
pet.color =3D keys[:color]
when :dog
pet.color =3D keys[:color]
pet.breed =3D keys[:breed]
when :ruby
pet.facets =3D 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 =3D Pet.allocate
pet.kind =3D kind
pet.name =3D name
# Messy solution. There is probably a better one.
get =3D lambda {|sym|
args.find(lambda{{}}) {|e|
e.kind_of? Hash && e[sym]
}[sym]
}
case(kind)
when :ham
pet.weight =3D get[:weight]
when :cat
pet.color =3D get[:color]
when :dog
pet.color =3D get[:color]
pet.breed =3D get[:breed]
when :ruby
pet.facets =3D get[:facets]
else
fail "Uknown kind of pet: #{kind}"
end
end
end

Pet.m1_create:)ham, "selfish_ham", weight:2.3)
Pet.m2_create(kind: :cat, name: "cat43", color: :black)
Pet.s_create:)dog, "singleton", color: :brown, breed: :mini_pincher)
Pet.s_create(kind: :ruby, name: "JRuby", facets: 26)

My s_ method is messy and could probably be cleaned up but it still
serves a point. Savor the style for a bit. It might add more verbosity
but I think it gives us some good side effects for the small price
(IMHO again). I think some really good points can be made for both
side but my _feeling_ is that Ruby doesn't need another halfway there
feature (IMHO). Keyword arguments are serious things and should be
treated as part of your interface (IMHO). I feel that the semantics of
m_ are more clear than the at first simpler look of s_ (IMHO -- why
not just automatically append these till the end of my message). It is
a hard choice. We still have one more option that I know of, change
nothing. Hashes seem to get the job done for most people already. I
know I missed something so please add to this. If I made any errors
please correct them. Just avoid and unproductive and personal attacks
please.

Thanks for reading this far,
Brian.
 
R

Robert Klemme

Brian said:
Hello fellow rubyists,

What I have bellow is what started as a post to RedHanded. It was
growing in size too rapidly so I decided to post here for all to see.
Sorry for starting yet another thread on these topics. It is rough so
please don't nit pick details. I don't want this to start a flame war
(though I can't do much about that now). I would rather see some ideas
on how to get the best of both worlds. Some of this won't come without
a compromise so keep that in mind. I apologize in advance if I did
make any grievous errors in my interpretations.

There is a matter of taste involved but beyond that there are a few
easy comparisons. I will try to keep this down to just that (though a
few may be on a grey line, I hope they are clear enough).

Let me cite Matz's slides first:
* Make method calls more descriptive
* Order free arguments

With that simple goal in mind, lets start the comparisons.

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. 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. 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_.

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. 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}]. 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).

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]
}
case(kind)
when :ham
pet.weight = get[:weight]
when :cat
pet.color = get[:color]
when :dog
pet.color = get[:color]
pet.breed = get[:breed]
when :ruby
pet.facets = get[:facets]
else
fail "Uknown kind of pet: #{kind}"
end
end
end

Pet.m1_create:)ham, "selfish_ham", weight:2.3)
Pet.m2_create(kind: :cat, name: "cat43", color: :black)
Pet.s_create:)dog, "singleton", color: :brown, breed: :mini_pincher)
Pet.s_create(kind: :ruby, name: "JRuby", facets: 26)

My s_ method is messy and could probably be cleaned up but it still
serves a point. Savor the style for a bit. It might add more verbosity
but I think it gives us some good side effects for the small price
(IMHO again). I think some really good points can be made for both
side but my _feeling_ is that Ruby doesn't need another halfway there
feature (IMHO). Keyword arguments are serious things and should be
treated as part of your interface (IMHO). I feel that the semantics of
m_ are more clear than the at first simpler look of s_ (IMHO -- why
not just automatically append these till the end of my message). It is
a hard choice. We still have one more option that I know of, change
nothing. Hashes seem to get the job done for most people already. I
know I missed something so please add to this. If I made any errors
please correct them. Just avoid and unproductive and personal attacks
please.

Thanks for reading this far,
Brian.

Wow! Quite a comprehensive discussion. Thanks for writing this far! :)

I think I have to digest this a bit, here are some first reading thoughts:

- Example for mixing keyword and positional args: like you showed, pos
args for mandatory, keyword args for multiple optional values.

- I guess Sydney's argument scheme will cause serious performance
drawbacks if there are multiple keyword arguments because there is one
hash per pair.

- Mixing both styles for a single method feels not right in Ruby because
it seems more complicated than necessary (just look at the length of the
part of your discussion that deals with this). Stored procedures of RDBMS
can usually be called with parameters given either positional style *or*
keyword style (btw, without a difference in declaration) but not mixed. I
would only allow one of the two approaches.

- Personally I'd probably favour an approach which allows only one
calling style at a time. Variables that have no default values declared
will just be nil. Basically this would be the equivalent of this with
nicer syntax:

old:
def foo(h)
h[:length] * h[:width]
end

foo:)length => 10, :width => 20)

new:
def foo(length, width)
length * width
end
foo 10, 20
foo width:20, length:10

Disclaimer: these remarks might not be too well thought out.

Kind regards

robert
 
C

Christophe Grandsire

Selon Brian Mitchell said:
My s_ method is messy and could probably be cleaned up but it still
serves a point. Savor the style for a bit. It might add more verbosity
but I think it gives us some good side effects for the small price
(IMHO again). I think some really good points can be made for both
side but my _feeling_ is that Ruby doesn't need another halfway there
feature (IMHO). Keyword arguments are serious things and should be
treated as part of your interface (IMHO). I feel that the semantics of
m_ are more clear than the at first simpler look of s_ (IMHO -- why
not just automatically append these till the end of my message). It is
a hard choice. We still have one more option that I know of, change
nothing. Hashes seem to get the job done for most people already. I
know I missed something so please add to this. If I made any errors
please correct them. Just avoid and unproductive and personal attacks
please.

Thanks for the post Brian. I want to say that I agree with your conclusio=
n that
matz's proposal is right now the best one. As I said on RedHanded, it's i=
ndeed
a question of interface. Keyword arguments are, when looking at them from=
an
interface point of view, like Smalltalk or Objective-C's multiword method=
s:
keyword arguments are part of the *name* of the method (that's even stron=
ger
than being part of its signature IMHO). Positional arguments are *not* pa=
rt of
the name of the function. Their position is part of its signature, but th=
at's
all. Mixing both styles, positional and keyword, is thus a bad idea IMHO.=
It's
mixing things that exist on different levels. Keyword arguments, being ju=
st a
part of the name of a method, must be explicitly marked as such by the
developer, and shouldn't be allowed to be left out by the user, just like=
you
can't leave out the name of a method.

Implicitly making all arguments keyword arguments is like implicitly maki=
ng all
arguments names part of the name of the method: it is actually more restr=
ictive
than having to explicitly mark the keyword arguments (then you can at lea=
st
choose which arguments are part of the name of the method and which not).
Adding to that the possibility to call keyword arguments in a positional =
way
and you get a recipe for disaster. If, as a developer, you want some argu=
ments
to be keyword arguments, make them explicitly so: *mean* it. Having half-=
baked
semi-keyword-semi-positional arguments is like not being able to choose b=
etween
two desserts on the menu, after an already heavy dinner: choose one or th=
e
other, but don't take both. Besides the higher check, there's quite a cha=
nce
you'd end up sick.
--
Christophe Grandsire.

http://rainbow.conlang.free.fr

It takes a straight mind to create a twisted conlang.
 
C

Christophe Grandsire

Selon Robert Klemme said:
- Mixing both styles for a single method feels not right in Ruby becau= se
it seems more complicated than necessary (just look at the length of th= e
part of your discussion that deals with this). Stored procedures of RD= BMS
can usually be called with parameters given either positional style *or= *
keyword style (btw, without a difference in declaration) but not mixed.= I
would only allow one of the two approaches.

I'm afraid that would be too restrictive for Ruby. There are already case=
s (see
Rails for instance, as I learned it in another thread) where methods have
positional arguments followed by hash arguments, which may be advantagedl=
y
replaced with positional arguments followed by keyword arguments. I think=
it
wouldn't fit with Ruby's dynamic argument passing to make positional and
keyword styles exclude each other.

On the other hand, you are right about one thing: if one is to allow all
arguments to be called either positionally or by keyword (the Sydney appr=
oach),
then the positional and keyword styles *must* exclude each other complete=
ly.
This is the only way we can prevent confusion.

However, I still think keyword arguments should be explicitly defined as =
such.
- Personally I'd probably favour an approach which allows only one
calling style at a time. Variables that have no default values declare= d
will just be nil. Basically this would be the equivalent of this with
nicer syntax:

old:
def foo(h)
h[:length] * h[:width]
end

foo:)length =3D> 10, :width =3D> 20)

new:
def foo(length, width)
length * width
end
foo 10, 20
foo width:20, length:10

Although I feel it's a bit too restrictive for Ruby, I could settle for t=
hat if
implicit keyword arguments win the debate. But I still prefer explicit on=
es.

On a side note, has the {a : 10, b : 20} alternative syntax for hashes th=
at I've
seen proposed a long time ago been adopted?
--
Christophe Grandsire.

http://rainbow.conlang.free.fr

It takes a straight mind to create a twisted conlang.
 
D

Daniel Schierbeck

I still haven't given up on my own style :)

def foo:)a, :b, :c = 3, **keys); end

# these should all be valid
foo :a => 1, :b => 2 # current style
foo a: 1, b: 2 # matz' proposition
foo :a = 1, :b = 2 # my style :D


1) *Method definition*
I think named arguments should have the same
syntax as positional ones, just with a colon
in front of it.

def foo(a, b, :c, :d = "bar")

2) *Method calling*
I think matz' style is preferable in many cases,
but i still think the :key = value syntax should
be there.

h = Human.new "John Doe", :height = 178, :weight = 65 # metric ;)
car.drive to: LasVegas, :speed = 125


Cheers,
Daniel
 
D

Damphyr

Daniel said:
I still haven't given up on my own style :)

def foo:)a, :b, :c = 3, **keys); end

# these should all be valid foo :a => 1, :b => 2 # current style
foo a: 1, b: 2 # matz' proposition foo :a = 1, :b = 2 #
my style :D snip
2) *Method calling* I think matz' style is preferable in many
cases, but i still think the :key = value syntax should be there.

h = Human.new "John Doe", :height = 178, :weight = 65 # metric ;)
car.drive to: LasVegas, :speed = 125
I can't say I can contribute much to the theoretical discussion. I can
only say what my first impressions are (up until Brian's email I was a
helpless spectator in an incomprehensible discussion).
The :a syntax looks a lot like symbols, which is something I would
avoid, in order to avoid paragraphs in documentation explaining that "in
method declarations :x means keyword", "when calling methods :a=x is a
keyword parameter assignment while :a is a symbol parameter" etc.
Cheers,
V.-
--
http://www.braveworld.net/riva

____________________________________________________________________
http://www.freemail.gr - äùñåÜí õðçñåóßá çëåêôñïíéêïý ôá÷õäñïìåßïõ.
http://www.freemail.gr - free email service for the Greek-speaking.
 
T

Trans

Why not a Parameters class. This would allow cool things like:

a = [ :a, :b, :c ]

def foo(a.to_parm) #as def foo(a,b,c)
#...
end

And it would be more useful in the def too (Although maybe this would
be a different class):

def foo(**parm)
p parm.arguments
p parm.named_arguments
p parm.block.call
end

foo(1,2,:a=>3){ "block" }

=> [1,2]
{:a=>3}
"block"

I'm not so keen on sticking with symbols in the parameters list for
named parameters. I think there's too much ':' run-on. Things like this
are aweful:

foo set: :vital, sort: :reverse, mod: :eek:dds do |a: :express|
# ...
end

Short of getting a new demarker for Symbol, I think it would be much
better to do without them in parameters if possible. This would work
fine I think:

def foo( a, b, c, x=>nil, y=>nil, z=>nil )
# ...
end

The def can be a shortened too:

def foo( a, b, c, x=>, y=>, z=> )
# ...
end

And call them in the same way:

foo( 1, 2, 3, x=>8, y=>9, z=>10 )

Which is *almost* exactly what we do now with hash parameters.

T.
 
D

Daniel Berger

Brian said:
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]
}

<snip>

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
 
D

Daniel Berger

Trans said:
Why not a Parameters class. This would allow cool things like:

a = [ :a, :b, :c ]

def foo(a.to_parm) #as def foo(a,b,c)
#...
end

And it would be more useful in the def too (Although maybe this would
be a different class):

def foo(**parm)
p parm.arguments
p parm.named_arguments
p parm.block.call
end

foo(1,2,:a=>3){ "block" }

=> [1,2]
{:a=>3}
"block"

Sydney will accomplish this via behaviors:

# This is *not* finalized
def foo(*parm)
p KeywordBehavior.arguments
p KeywordBehavior.keyword_arguments
end

Abstracting it out to a generic "Parameter" behavior is something I
hadn't thought of, but knowing Evan, it just might be possible. :)

Regards,

Dan
 
D

David A. Black

Hi --

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.

I don't agree. If I write this:

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

I expect to be "allowed" to rename my parameters at will without
breaking any calling code. I'd even expect to be allowed to change,
say, (a,b,*c) to *args if I felt there was a good reason to.

I don't want my choice of local variable names to be coupled to
calling code. If I offer keywords or hash keys, that means I've
decided that I *do* want to publish those names, and commit to them.
Otherwise I consider them strictly the method's business, not the
caller's.


David
 
C

Christophe Grandsire

Selon Damphyr said:
I can't say I can contribute much to the theoretical discussion. I can
only say what my first impressions are (up until Brian's email I was a
helpless spectator in an incomprehensible discussion).
The :a syntax looks a lot like symbols, which is something I would
avoid, in order to avoid paragraphs in documentation explaining that "i= n
method declarations :x means keyword", "when calling methods :a=3Dx is = a
keyword parameter assignment while :a is a symbol parameter" etc.

I agree. Also, the (a: "foo") syntax looks a lot like Smalltalk's named
arguments, and that gives it instant recognition.
--
Christophe Grandsire.

http://rainbow.conlang.free.fr

It takes a straight mind to create a twisted conlang.
 
C

Christophe Grandsire

Selon "David A. Black said:
I don't agree. If I write this:

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

I expect to be "allowed" to rename my parameters at will without
breaking any calling code. I'd even expect to be allowed to change,
say, (a,b,*c) to *args if I felt there was a good reason to.

I don't want my choice of local variable names to be coupled to
calling code. If I offer keywords or hash keys, that means I've
decided that I *do* want to publish those names, and commit to them.
Otherwise I consider them strictly the method's business, not the
caller's.

Amen. As I keep saying, it's a matter of interface. If you allow implicit
keyword arguments, you're suddenly mixing interface and implementation, a=
nd
that is *wrong* (in the sense that it is difficult to maintain, and it br=
eaks
the promise of encapsulation). However, if you explicitly decide that som=
e
argument is a keyword argument, you have *consciously* made it part of th=
e
interface (I'd rather say part of the very *name* of the method), so you =
can
still maintain the separation between interface and implementation to the=
level
you want as a developer.

And I disagree that Ruby favours implicitness. It favours dynamicity, ope=
nness,
reflection, convention above configuration (and that's more a style commo=
n to
many Rubyists and well exemplified in Rails). But that doesn't imply
implicitness (pardon the pun, it was unintented). If anything, the fact t=
hat
Ruby is strongly typed, even if dynamically typed, and hardly does any im=
plicit
casting (except among numbers, especially between Fixnum and Bignum), sho=
uld be
indicative that Ruby, if anything, *does not favour implicitness*.
--
Christophe Grandsire.

http://rainbow.conlang.free.fr

It takes a straight mind to create a twisted conlang.
 
T

Trans

"Ah, to Hek with interfaces. They're overrated anyways!", says Typo the
Duck. "You can Duckument cant you?"

def phooey
unless %how
"This thing quacks like a #{%1}."
else
"This thing #{%how} like a #{%1}."
end
end

phooey( "duck" )
=> "This thing quacks like a duck."

phooey( "duck", how=>"looks" )
=> "This thing looks like a duck."

:)

T.
 
S

Sean O'Halpin

I expect to be "allowed" to rename my parameters at will without
breaking any calling code. I'd even expect to be allowed to change,
say, (a,b,*c) to *args if I felt there was a good reason to.

I'm with you on this David. I find myself doing (a,b,*args) =3D> (*args)
quite often.

Regards,

Sean
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: A comparison by example of keyword argument styles"

|1) *Method definition*
| I think named arguments should have the same
| syntax as positional ones, just with a colon
| in front of it.
|
| def foo(a, b, :c, :d = "bar")

Why with a colon in front of it instead of right after it?

|2) *Method calling*
| I think matz' style is preferable in many cases,
| but i still think the :key = value syntax should
| be there.

Why should be?

matz.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: A comparison by example of keyword argument styles"

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

Too much implicitness makes code cryptic. Too much explicitness makes
code verbose. It's a matter of balance.

By the way, how do you delegate the whole arguments (including keyword
arguments) in your style? Using KeywordBehavior?

Besides, what would happen for the following code?

def foo(*args)
# how do you delegate arguments?
bar(*args)
end

def bar(a,b,c)
p [a,b,c]
end

foo(1,a:2,c:3)

and

class Foo
def foo(a,b,c)
p [a,b,c]
end
end
class Bar<Foo
# what if argument names differ?
def foo(d,e,f)
super
end
end
Bar.new.foo(d:1,e:2,f:3)

matz.
 
D

Daniel Berger

Yukihiro said:
Hi,

In message "Re: A comparison by example of keyword argument styles"

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

Too much implicitness makes code cryptic. Too much explicitness makes
code verbose. It's a matter of balance.

By the way, how do you delegate the whole arguments (including keyword
arguments) in your style? Using KeywordBehavior?

Besides, what would happen for the following code?

def foo(*args)
# how do you delegate arguments?
bar(*args)
end

def bar(a,b,c)
p [a,b,c]
end

foo(1,a:2,c:3)

Not allowed because redefinition of the same parameter raises an error. Here
you're trying to define 'a' twice, once as the first positional argument, once
as a named parameter.

For purposes of this argument, though, let's say you did this:

foo(b:1, a:2, c:3)

Then, if we inspect '*args' in foo, it looks like [{:b=>1, :a=>2, :c=>3}]

For now, we're toying with the idea of an explicit behavior call to handle
splat args in this case. So, the method definition would look like this:

def foo(*args)
bar(*KeywordBehavior.arguments) # Would pass 2, 1, 3
end

Yes, it's uglier. How does it work in your implementation? I'm curious to know
how you preserve argument order.
and

class Foo
def foo(a,b,c)
p [a,b,c]
end
end
class Bar<Foo
# what if argument names differ?
def foo(d,e,f)
super
end
end
Bar.new.foo(d:1,e:2,f:3)

matz.

We discussed this somewhat last night. There are a couple of possible routes
to take.

1) Insist that parameter names must match, and raise an error.
2) Pass the arguments positionally.

I think we're still undecided if I recall correctly.

Let me ask you the same thing. What happens here with your implementation?

class Foo
def foo(a:, b:, c:)
p [a,b,c]
end
end

class Bar < Foo
def foo(d:, e:, f:)
super
end
end

Bar.new.foo(f:3, e:2, d:1)

Regards,

Dan
 
B

Brian Mitchell

Brian said:
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 a= rguments:

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=3D>1, :k2=3D>2, :k3=3D>3}]. In Matz's
implementation, there is no '*args' afaik, only '**keys', which is
{:k1=3D>, :k2=3D>2, :k3=3D>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 =3D> 3}, = {
:b =3D> 4}] if I understand correctly. m_ gives you [1,2, {:a =3D> 3, := b
=3D> 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 t= ime.
class Pet
def self.m1_create(kind, name, **keys)
pet =3D Pet.allocate
pet.kind =3D kind
pet.name =3D name
case(kind)
when :ham
pet.weight =3D keys[:weight]
when :cat
pet.color =3D keys[:color]
when :dog
pet.color =3D keys[:color]
pet.breed =3D keys[:breed]
when :ruby
pet.facets =3D 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 =3D Pet.allocate
pet.kind =3D kind
pet.name =3D name
# Messy solution. There is probably a better one.
get =3D lambda {|sym|
args.find(lambda{{}}) {|e|
e.kind_of? Hash && e[sym]
}[sym]
}

<snip>

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 =3D 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

Thanks for the reply. This clears the picture up a bit for me.

Brian.
 
B

Brian Mitchell

Hello again,

I am updating my original post with more information, namely some
corrections for s_ and some new discussion at the bottom. I am will
say again that the stuff bellow probably still contains errors but I
hope that it is at least more accurate than my original. I left all
the original text there so we could have a nice comparison of now and
then. It also serves as a good example of how something might work for
an implementation that differs in a way that is congruent to my
original post.

Hello fellow rubyists,

What I have bellow is what started as a post to RedHanded. It was
growing in size too rapidly so I decided to post here for all to see.
Sorry for starting yet another thread on these topics. It is rough so
please don't nit pick details. I don't want this to start a flame war
(though I can't do much about that now). I would rather see some ideas
on how to get the best of both worlds. Some of this won't come without
a compromise so keep that in mind. I apologize in advance if I did
make any grievous errors in my interpretations.

There is a matter of taste involved but beyond that there are a few
easy comparisons. I will try to keep this down to just that (though a
few may be on a grey line, I hope they are clear enough).

Let me cite Matz's slides first:
* Make method calls more descriptive
* Order free arguments

With that simple goal in mind, lets start the comparisons.

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. 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 arg= uments:

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. 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_.

This was one part confusing me about s_. When we get our list it could
either be:

[1,2,3,4] or [{:k1 =3D> 1, :k2 =3D> 2, :k3 =3D> 3}]

The problem is still there but in variation, the arguments might or
might not be in the hash. This blurring the line between the method
and the caller, which are currently two very separated things.
Checking this for an interface that handles not so simple calls might
be a burden more than an assistance. I will keep my conclusion between
the two.

Another problem brought to my attention by Matz's post is delegation.
One can not use this to delegate unless you keep you method interface
the same, this could be problematic over long chains of argument
passing (i.e. super could have unintended meaning now)

To clarify m_'s behavior I will copy some examples from Matz's slides
with some annotations:

def baz(*rest, a:4, b:0, **keys)
...
end

baz() # rest=3D[], a=3D4, b=3D0, keys=3D{} <- notice that [] in args has an
implicit {} for **keys
baz(1) # rest=3D[1], a=3D4, b=3D0, keys=3D{} <- explicit keys do not count =
as
positionals
baz(a:1) # rest=3D[{a:1}], a=3D1, b=3D0, keys=3D{a:1} <- * will always cont=
ain
the full set of passed variables.
baz(a:1, b:2) # rest=3D[{a:1, b:2}], a=3D1, b=3D2, keys=3D{a:1, b:2}
baz(1, 2, b:2) # rest=3D[1, 2, {b:2}], a=3D4, b=3D2, keys=3D{b:2} <-
interesting. This result is for passing on correct values for
delegation.
baz(c:2) # rest=3D[{c:2}], a=3D4, b=3D0, keys=3D{c:2} <- another one to thi=
nk about.
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. 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.

I will extend this with another way to explain it. *args means you can
also pass any number of keys also. This goes for both. This means
there is no way of having only variable numbers of positional
arguments and static numbers of keyword arguments.

m_f4(1,2,3, a:4, b:5, c:6) <- example of what I mean.
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 =3D> 3}, {
:b =3D> 4}] if I understand correctly. m_ gives you [1,2, {:a =3D> 3, :b
=3D> 4}]. 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.

I was wrong on this one. Let me try again.

For s_ you get: [1,2, { :a =3D> 3, :b =3D> 4}] if I understand correctly.
m_ gives you [1,2, {:a =3D> 3, :b =3D> 4}]. They seem to yield the same
result but let me give a new the s_ example to show how they are
practically the same:

def s_f6(a, b, *args)

s_f6(0, 1, 3, b:2)

s_f6's *args would yield something like [3, {:b =3D> 2}] in this case
(If I get Daniel's reply correctly). a =3D 0, b =3D 1

s_f6(1, 3, a:0, b:2)

This time *args would look like [1, 3, {:b =3D> 2}]. a =3D 0, b =3D 1.

This is an example of where it doesn't break which is in contrast to
my original interpretation. It seems neither would win here after
further analysis. It is a matter of style for this example.
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).

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 tim= e.
class Pet
def self.m1_create(kind, name, **keys)
pet =3D Pet.allocate
pet.kind =3D kind
pet.name =3D name
case(kind)
when :ham
pet.weight =3D keys[:weight]
when :cat
pet.color =3D keys[:color]
when :dog
pet.color =3D keys[:color]
pet.breed =3D keys[:breed]
when :ruby
pet.facets =3D 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

This method is much cleaner now:
def self.s_create(kind, name, *args)
pet =3D Pet.allocate
pet.kind =3D kind
pet.name =3D name

get =3D args.last
case(kind)
when :ham
pet.weight =3D get[:weight]
when :cat
pet.color =3D get[:color]
when :dog
pet.color =3D get[:color]
pet.breed =3D get[:breed]
when :ruby
pet.facets =3D get[:facets]
else
fail "Uknown kind of pet: #{kind}"
end
end
end

Pet.m1_create:)ham, "selfish_ham", weight:2.3)
Pet.m2_create(kind: :cat, name: "cat43", color: :black)
Pet.s_create:)dog, "singleton", color: :brown, breed: :mini_pincher)
Pet.s_create(kind: :ruby, name: "JRuby", facets: 26)

My s_ method is messy and could probably be cleaned up but it still
serves a point. Savor the style for a bit. It might add more verbosity
but I think it gives us some good side effects for the small price
(IMHO again). I think some really good points can be made for both
side but my _feeling_ is that Ruby doesn't need another halfway there
feature (IMHO). Keyword arguments are serious things and should be
treated as part of your interface (IMHO). I feel that the semantics of
m_ are more clear than the at first simpler look of s_ (IMHO -- why
not just automatically append these till the end of my message). It is
a hard choice. We still have one more option that I know of, change
nothing. Hashes seem to get the job done for most people already. I
know I missed something so please add to this. If I made any errors
please correct them. Just avoid and unproductive and personal attacks
please.

I've updated quite a bit of the code and commentary to reflect things.
The last comparison is mostly equal however, it does show some
ambiguities with calling in the future for both methods:

def foo(*args) ... end

foo(1, 2, a:3, b:4)
foo(1, 2, {:a =3D> 3, :b =3D> 4})

*args gets the same array in both cases.

Now before Daniel goes off on me about behaviors and how it handles
things ;) ... lets me continue with more evolution happening in the
community:

Behaviors allow Sydney to implement a prototype to these types of
behaviors. However, since they are a separate change to the language I
will leave it to someone else to give an overview of what they do and
don't give use. The interesting part is where Evan is taking his
implementation.

Currently, using a basic array for *args can cause ambiguous calls.
This could be solved by attaching an empty hash at the end of args in
all cases or maybe only certain cases (though something more
dependable is likely to be less work for the human mind). This make it
feel like we are continuously trying to fix the wrong approach to
keyword arguments (I speak for myself only).

After speaking with Evan a little about his plans in more detail, he
discussed that he is considering using a new kind of object in place
of an Array for *. The new class (which I will call Arguments), would
still act like an array (* would still work for expansion into
delegated calls). However, one could now go:

def bar(*rest)
rest.keywords
... etc ... # I am sure we could come up with a suitable interface.
end

This is the exact kind of thing I thing we should be looking for. I
don't know how Matz would feel about it. In fact I still am not sure
myself as I haven't tried using it in examples yet. The point stands
that there might be a good compromise to make between s_ and m_. This
goes a long ways towards that IMO.

Evan says he will be working on an implementation of this for Sydney.
I look forward to testing it live rather than typing code into GMail.
Matz, are there any patches that implement your proposal yet? I would
love to test that out too. If not anyone volunteer? I know I would be
wasting my time trying as I am not that much of an internals wizard.

Thanks,
Brian.
 
T

Trans

<quote author="Brian">
After speaking with Evan a little about his plans in more detail, he
discussed that he is considering using a new kind of object in place
of an Array for *. The new class (which I will call Arguments), would
still act like an array (* would still work for expansion into
delegated calls). However, one could now go:

def bar(*rest)
rest.keywords
... etc ... # I am sure we could come up with a suitable interface.
end

This is the exact kind of thing I thing we should be looking for.
</quote>

Ah, now that's a breath of fresh air. And very nice job of combining
new class with Array behavior for backward compatiblity. Intersting
approach. Please let us know how it proceeds.

T.
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top