A comparison by example of keyword argument styles

Discussion in 'Ruby' started by Brian Mitchell, Oct 21, 2005.

  1. 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.
     
    Brian Mitchell, Oct 21, 2005
    #1
    1. Advertising

  2. Brian Mitchell wrote:
    > 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
     
    Robert Klemme, Oct 21, 2005
    #2
    1. Advertising

  3. Selon Brian Mitchell <>:

    >
    > 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.
     
    Christophe Grandsire, Oct 21, 2005
    #3
  4. Selon Robert Klemme <>:

    >
    > - 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.
     
    Christophe Grandsire, Oct 21, 2005
    #4
  5. 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
     
    Daniel Schierbeck, Oct 21, 2005
    #5
  6. Brian Mitchell

    Damphyr Guest

    Daniel Schierbeck wrote:
    > 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.
     
    Damphyr, Oct 21, 2005
    #6
  7. Brian Mitchell

    Trans Guest

    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.
     
    Trans, Oct 21, 2005
    #7
  8. Brian Mitchell wrote:
    > Hello fellow rubyists,


    <snip>

    > 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
     
    Daniel Berger, Oct 21, 2005
    #8
  9. Trans wrote:
    > 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
     
    Daniel Berger, Oct 21, 2005
    #9
  10. Hi --

    On Fri, 21 Oct 2005, Daniel Berger wrote:

    > Brian Mitchell wrote:
    >> Hello fellow rubyists,

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


    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

    --
    David A. Black
     
    David A. Black, Oct 21, 2005
    #10
  11. Selon Damphyr <>:

    > 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.
     
    Christophe Grandsire, Oct 21, 2005
    #11
  12. Selon "David A. Black" <>:

    > >
    > > Ruby has always favored implicitness over explicitness. The fact tha=

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


    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.
     
    Christophe Grandsire, Oct 21, 2005
    #12
  13. Brian Mitchell

    Trans Guest

    "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.
     
    Trans, Oct 21, 2005
    #13
  14. On 10/21/05, David A. Black <> wrote:
    > 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
     
    Sean O'Halpin, Oct 21, 2005
    #14
  15. Hi,

    In message "Re: A comparison by example of keyword argument styles"
    on Fri, 21 Oct 2005 18:46:59 +0900, Daniel Schierbeck <> writes:

    |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.
     
    Yukihiro Matsumoto, Oct 21, 2005
    #15
  16. Hi,

    In message "Re: A comparison by example of keyword argument styles"
    on Fri, 21 Oct 2005 20:12:08 +0900, "Daniel Berger" <> writes:

    |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.
     
    Yukihiro Matsumoto, Oct 21, 2005
    #16
  17. Yukihiro Matsumoto wrote:
    > Hi,
    >
    > In message "Re: A comparison by example of keyword argument styles"
    > on Fri, 21 Oct 2005 20:12:08 +0900, "Daniel Berger" <> writes:
    >
    > |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
     
    Daniel Berger, Oct 21, 2005
    #17
  18. On 10/21/05, Daniel Berger <> wrote:
    > Brian Mitchell wrote:
    > > Hello fellow rubyists,

    >
    > <snip>
    >
    > > 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.
     
    Brian Mitchell, Oct 21, 2005
    #18
  19. 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.

    On 10/21/05, Brian Mitchell <> wrote:
    > 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.
     
    Brian Mitchell, Oct 21, 2005
    #19
  20. Brian Mitchell

    Trans Guest

    <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.
     
    Trans, Oct 21, 2005
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Oktay Safak
    Replies:
    2
    Views:
    1,347
    Oktay Safak
    Aug 2, 2003
  2. Steven Bethard

    keyword argument for min/max

    Steven Bethard, Nov 30, 2004, in forum: Python
    Replies:
    1
    Views:
    320
    Steven Bethard
    Nov 30, 2004
  3. Replies:
    6
    Views:
    478
    Peter Otten
    May 10, 2007
  4. Hamilton, William

    RE: keyword checker - keyword.kwlist

    Hamilton, William, May 10, 2007, in forum: Python
    Replies:
    4
    Views:
    372
  5. Deepu
    Replies:
    1
    Views:
    260
    ccc31807
    Feb 7, 2011
Loading...

Share This Page