[RCR] nil for unassigned keys

Sometimes I find myself writing :key=>true,
it would be nice if one didn't had to do assignment,
so its value instead defaulted to nil.

def test(hash={})
  p hash
end

test('x', 'y'=>2, 'z') # {"x"=>nil, "y"=>2, "z"=>nil}

Is this too far out?

···

--
Simon Strandgaard

Hi,

···

In message "Re: [RCR] nil for unassigned keys" on Tue, 19 Jul 2005 20:40:47 +0900, Simon Strandgaard <neoneye@gmail.com> writes:

Sometimes I find myself writing :key=>true,
it would be nice if one didn't had to do assignment,
so its value instead defaulted to nil.

def test(hash={})
p hash
end

test('x', 'y'=>2, 'z') # {"x"=>nil, "y"=>2, "z"=>nil}

In that case, how can we distinguish a hash key without value and
ordinal mandatory argument?

              matz.

Maybe I don't understand the mandatory argument thing?
But can't you just count them somehow?

def test(arg1, arg2, hash={})
  puts "#{arg1.inspect} #{arg2.inspect} #{hash.inspect}"
end

# mandatory argument?
test(1, 2) # 1 2 {}

# hash key without value
test(1, 2, 42) # 1 2 {42=>nil}
test(1, 2, 3, 4) # 1 2 {3=>nil, 4=>nil}
test(1, 2, 3, 4=>5) # 1 2 {3=>nil, 4=>5}

···

On 7/19/05, Yukihiro Matsumoto <matz@ruby-lang.org> wrote:

    on Tue, 19 Jul 2005 20:40:47 +0900, Simon Strandgaard <neoneye@gmail.com> writes:

>Sometimes I find myself writing :key=>true,
>it would be nice if one didn't had to do assignment,
>so its value instead defaulted to nil.
>
>def test(hash={})
> p hash
>end
>
>test('x', 'y'=>2, 'z') # {"x"=>nil, "y"=>2, "z"=>nil}

In that case, how can we distinguish a hash key without value and
ordinal mandatory argument?

--
Simon Strandgaard

Hi,

Maybe I don't understand the mandatory argument thing?
But can't you just count them somehow?

Hmm, the current rule is that all arguments are packed in an array,
and you can specify a hash without braces at the end of the argument
list, so that

test(1, 2, 42) # [1,2,42]
test(1, 2, 3, 4) # [1,2,3,4]
test(1, 2, 3, 4=>5) # [1,2,3,{4=>5}]

are passed by the caller. This does not require any callee side
argument tweaking. I think I can understand the rule you want, but
it's rather complex, especially in combination with optional arguments
and rest argument.

              matz.

···

In message "Re: [RCR] nil for unassigned keys" on Tue, 19 Jul 2005 21:58:53 +0900, Simon Strandgaard <neoneye@gmail.com> writes:

not when there are zero or more allowed, for instance

   def test(*args, &block)
     x = args.shift || 42
     hash = Hash === x ? x : (args.shift || {})
     y, z = hash.values_at 'x', 'z'
     p [x,y,z]
   end

which allows

   test()
   test('y' => 42)
   test('y' => 42, 'z' => 42.0)
   test('x as forty-two')
   test('x as forty-two', 'y' => 42)
   test('x as forty-two', 'y' => 42, 'z' => 42.0)

maybe it's possible - but it seems like kleeny star makes it pretty tough.

why not a little helper function to transform argument lists in the way you
desire?

   def h(*list)
     list.inject({}) do |hash,elem|
       hash.update(Hash === elem ? {elem => nil} : elem)
     end
   end

which at least allows

   test(h('x', 'z', 'y' => 42))

though not

   test(h('x', 'y' => 42, 'z'))

cheers.

-a

···

On Tue, 19 Jul 2005, Simon Strandgaard wrote:

On 7/19/05, Yukihiro Matsumoto <matz@ruby-lang.org> wrote:

    on Tue, 19 Jul 2005 20:40:47 +0900, Simon Strandgaard <neoneye@gmail.com> writes:

>Sometimes I find myself writing :key=>true,
>it would be nice if one didn't had to do assignment,
>so its value instead defaulted to nil.
>
>def test(hash={})
> p hash
>end
>
>test('x', 'y'=>2, 'z') # {"x"=>nil, "y"=>2, "z"=>nil}

In that case, how can we distinguish a hash key without value and
ordinal mandatory argument?

Maybe I don't understand the mandatory argument thing?
But can't you just count them somehow?

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
My religion is very simple. My religion is kindness.
--Tenzin Gyatso

===============================================================================

Hi Simon,

Sometimes I find myself writing :key=>true,
it would be nice if one didn't had to do assignment,
so its value instead defaulted to nil.

def test(hash={})
  p hash
end

test('x', 'y'=>2, 'z') # {"x"=>nil, "y"=>2, "z"=>nil}

In that case, how can we distinguish a hash key
without value and ordinal mandatory argument?

Theoretically, you could allow flag arguments *after* the
first hash entry,

   def test(hash={}) ... end

   test :x => 2, :y, :z

but then of course you wouldn't be able to pass flag
arguments *only*.

Maybe I don't understand the mandatory argument thing?
But can't you just count them somehow?

def test(arg1, arg2, hash={})
  puts "#{arg1.inspect} #{arg2.inspect} #{hash.inspect}"
end

# mandatory argument?
test(1, 2) # 1 2 {}

# hash key without value
test(1, 2, 42) # 1 2 {42=>nil}
test(1, 2, 3, 4) # 1 2 {3=>nil, 4=>nil}
test(1, 2, 3, 4=>5) # 1 2 {3=>nil, 4=>5}

You can always do this:

   class Array
     def butlast(n=1) slice 0 ... -n end
     def to_option_hash
       if last.kind_of? Hash
       then hash = last ; flags = butlast
       else hash = {} ; flags = self end
       for flag in flags do
         flag.to_sym or raise ArgumentError,
           "cannot convert flag option `#{flag}' to symbol"
         hash[flag.to_sym] = true
       end
       hash
     end
   end

   def test(arg1, arg2, *args)
     options = args.to_option_hash
     puts "#{arg1.inspect} #{arg2.inspect} #{options.inspect}"
   end

That reminds me, I promised I would write an RCR for
allowing trailing argument after the splatting one, like so:

   def moomin(foo, *bar, baz)

It was said that optional arguments should be allowed before
the splat, but not after it.

   def moomin(foo, optional=nil, *bar, baz)

In the above case of option hashes with flags, however, it
is obvious that it would be useful to be able to have
optional arguments after the splat.

   def test2(arg1, arg2, *flags, options={})

The above is unambiguous: we must always try to assign one
argument to ‘options’ — anything else wouldn't make sense.
It only becomes a problem when you have optional arguments
both before *and* after the splat:

   def test3(arg1, arg2, arg3=nil, *flags, o={})

Obviously, when ‘test3’ is called with four arguments, we
must assign the third to ‘arg3’ and the fourth to ‘options’,
but what if only three arguments are given — does the last
one go to ‘arg3’ or to ‘options’?

I think the only reasonable thing to do is to fill optional
argument slots from left to right, as is done now:

   def foo(a, b=nil, *c, d=nil, e) ... end

   foo 1 # ArgumentError
   foo 1, 2 # a=1, b=nil, c=, d=nil, e=2
   foo 1, 2, 3 # a=1, b=2, c=, d=nil, e=3
   foo 1, 2, 3, 4 # a=1, b=2, c=, d=3, e=4
   foo 1, 2, 3, 4, 5 # a=1, b=2, c=[3], d=4, e=5

This would also reasonably mean that mixing optional and
mandatory arguments would become allowed:

   def bar(a, b=nil, c, d=nil, e) ... end

   foo 1 # ArgumentError
   foo 1, 2 # ArgumentError
   foo 1, 2, 3 # a=1, b=nil, c=2, d=nil, e=3
   foo 1, 2, 3, 4 # a=1, b=2, c=3, d=nil, e=4
   foo 1, 2, 3, 4, 5 # a=1, b=2, c=3, d=4, e=5

What do people think? Does this all seem reasonable?
Eric Mahurin had another good example of a use case:

   def = *coordinates, value

···

--
Daniel Brockman <daniel@brockman.se>

    So really, we all have to ask ourselves:
    Am I waiting for RMS to do this? --TTN.

PS: Note how I've started putting parens in the argument
lists of method definitions and leaving out the spaces
around the equals signs for optional arguments. Somewhere,
Guido is chuckling with satisfaction. I hope you are too.

Ok, Thanks for explaining how it works.
I had no idea what it would take to add this..
Just thought that I had to give it a go.

···

On 7/19/05, Yukihiro Matsumoto <matz@ruby-lang.org> wrote:

    on Tue, 19 Jul 2005 21:58:53 +0900, Simon Strandgaard <neoneye@gmail.com> writes:

>Maybe I don't understand the mandatory argument thing?
>But can't you just count them somehow?

Hmm, the current rule is that all arguments are packed in an array,
and you can specify a hash without braces at the end of the argument
list, so that

test(1, 2, 42) # [1,2,42]
test(1, 2, 3, 4) # [1,2,3,4]
test(1, 2, 3, 4=>5) # [1,2,3,{4=>5}]

are passed by the caller. This does not require any callee side
argument tweaking. I think I can understand the rule you want, but
it's rather complex, especially in combination with optional arguments
and rest argument.

--
Simon Strandgaard

[snip]

> Maybe I don't understand the mandatory argument thing?
> But can't you just count them somehow?

not when there are zero or more allowed, for instance
   def test(*args, &block)

Ok.. yeah thats an issue.

[snip]

why not a little helper function to transform argument lists in the way you
desire?

[snip]

which at least allows
   test(h('x', 'z', 'y' => 42))

[snip]

pretty neat..

the reason I began thinking these thoughts was one
of the other threads where String#to_a taking a hash
was discussed.

"aoeu".to_a(:bytes=>true)

it could be a little nicer with

"aoeu".to_a(:bytes)

···

On 7/19/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

On Tue, 19 Jul 2005, Simon Strandgaard wrote:

--
Simon Strandgaard

Daniel Brockman schrieb:

...
That reminds me, I promised I would write an RCR for
allowing trailing argument after the splatting one, like so:

...
I think the only reasonable thing to do is to fill optional
argument slots from left to right, as is done now:

I think so, too.

...
This would also reasonably mean that mixing optional and
mandatory arguments would become allowed:

   def bar(a, b=nil, c, d=nil, e) ... end

   foo 1 # ArgumentError
   foo 1, 2 # ArgumentError
   foo 1, 2, 3 # a=1, b=nil, c=2, d=nil, e=3
   foo 1, 2, 3, 4 # a=1, b=2, c=3, d=nil, e=4
   foo 1, 2, 3, 4, 5 # a=1, b=2, c=3, d=4, e=5

I don't like this. It is hard to see which value is put into which parameter. Picking some arguments from the left and from the right and stuffing the rest in the splatted parameter is easier to grasp, at least for me. I think the chances to get the RCR accepted are higher if you drop this feature.

What do people think? Does this all seem reasonable?
Eric Mahurin had another good example of a use case:

   def = *coordinates, value

Yes. Please do write the RCR!

Regards,
Pit

Yukihiro Matsumoto wrote:

Simon Strandgaard <neoneye@gmail.com> writes:

> Maybe I don't understand the mandatory argument thing?
> But can't you just count them somehow?

Hmm, the current rule is that all arguments are packed in an array,
and you can specify a hash without braces at the end of the argument
list, so that

test(1, 2, 42) # [1,2,42]
test(1, 2, 3, 4) # [1,2,3,4]
test(1, 2, 3, 4=>5) # [1,2,3,{4=>5}]

are passed by the caller. This does not require any callee side
argument tweaking. I think I can understand the rule you want, but
it's rather complex, especially in combination with optional arguments
and rest argument.

Tangentially related: I thought Ruby 2.0 would be adding "true" keyword
arguments to parameter lists, i.e., not storing in them in a hash. If
so, how will this affect this discussion?,
        nikolai

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/\!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

ah. true - no pun intended. this is pretty short and works though

   "aoeu".to_a :bytes=>1

cheers.

-a

···

On Tue, 19 Jul 2005, Simon Strandgaard wrote:

the reason I began thinking these thoughts was one
of the other threads where String#to_a taking a hash
was discussed.

"aoeu".to_a(:bytes=>true)

it could be a little nicer with

"aoeu".to_a(:bytes)

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
My religion is very simple. My religion is kindness.
--Tenzin Gyatso

===============================================================================

Hi Pit,

Thank you for your comments.

This would also reasonably mean that mixing optional and
mandatory arguments would become allowed:

   def bar(a, b=nil, c, d=nil, e) ... end

   bar 1 # ArgumentError
   bar 1, 2 # ArgumentError
   bar 1, 2, 3 # a=1, b=nil, c=2, d=nil, e=3
   bar 1, 2, 3, 4 # a=1, b=2, c=3, d=nil, e=4
   bar 1, 2, 3, 4, 5 # a=1, b=2, c=3, d=4, e=5

I don't like this. It is hard to see which value is put
into which parameter. Picking some arguments from the left
and from the right and stuffing the rest in the splatted
parameter is easier to grasp, at least for me.

Then what about this?

   def baz(a, b=nil, c) ... end

Isn't that just as easy to grasp as this?

   def baz(a, *b, c) ... end

I think the chances to get the RCR accepted are higher if
you drop this feature.

You may be right. But I think restricting this to “only if
there's a splat argument somewhere!” is pretty arbitrary.

If you wanted the semantics of this,

   def baz(a, b=nil, c) ... end

you'd have to write it like this,

   def baz(a, *dummy, b=nil, c)
     dummy.empty? or raise ArgumentError
     ... end

which seems pretty annoying-for-no-reason.

···

--
Daniel Brockman <daniel@brockman.se>

    So really, we all have to ask ourselves:
    Am I waiting for RMS to do this? --TTN.

Thank you for your comments.
>> This would also reasonably mean that mixing optional and
>> mandatory arguments would become allowed:
>>
>> def bar(a, b=nil, c, d=nil, e) ... end
>>
>> bar 1 # ArgumentError
>> bar 1, 2 # ArgumentError
>> bar 1, 2, 3 # a=1, b=nil, c=2, d=nil, e=3
>> bar 1, 2, 3, 4 # a=1, b=2, c=3, d=nil, e=4
>> bar 1, 2, 3, 4, 5 # a=1, b=2, c=3, d=4, e=5
>
> I don't like this. It is hard to see which value is put
> into which parameter. Picking some arguments from the left
> and from the right and stuffing the rest in the splatted
> parameter is easier to grasp, at least for me.

Then what about this?

   def baz(a, b=nil, c) ... end

Isn't that just as easy to grasp as this?

   def baz(a, *b, c) ... end

> I think the chances to get the RCR accepted are higher if
> you drop this feature.

You may be right. But I think restricting this to “only if
there's a splat argument somewhere!” is pretty arbitrary.

If you wanted the semantics of this,

   def baz(a, b=nil, c) ... end

you'd have to write it like this,

   def baz(a, *dummy, b=nil, c)
     dummy.empty? or raise ArgumentError
     ... end

which seems pretty annoying-for-no-reason.

Daniel,

I thought you were going to submit an RCR for this (not too
related to the original RCR topic of this thread - nil for
unassigned keys - of a hash). Here is what was said in the
thread "Re: iterators and block arguments" (which covers your
concern above):

···

--- Daniel Brockman <daniel@brockman.se> wrote:

--- Eric Mahurin <eric_mahurin@yahoo.com> wrote:

--- Daniel Brockman <daniel@brockman.se> wrote:

> Maybe this syntax could be adopted?
>
> def foo a, b, *args, c, d; ... end

I want that too. It would be nice for the = method where
the
value is always the last argument. Or any method where you
want optional arguments first or in the middle.

In general, this:

def foo a, b, c=x, d=y, *other, e, f
    ...
end

should be equivalent to:

def foo a, b, *remaining
    e,f = remaining.slice!(-2,2)
    c = remaining.empty? ? x : remaining.shift
    d = remaining.empty? ? y : remaining.shift
    other = remaining
    ...
end

Want to submit an RCR for this? I've already done my fair
share.

____________________________________________________
Start your day with Yahoo! - make it your home page
http://www.yahoo.com/r/hs

Hi Daniel.

Then what about this?

   def baz(a, b=nil, c) ... end

Isn't that just as easy to grasp as this?

   def baz(a, *b, c) ... end

Yes it is. It was the following definition, which was confusing to mee:

    def bar(a, b=nil, c, d=nil, e) ... end

I think restricting this to “only if
there's a splat argument somewhere!” is pretty arbitrary.

If you wanted the semantics of this,

   def baz(a, b=nil, c) ... end

you'd have to write it like this,

   def baz(a, *dummy, b=nil, c)
     dummy.empty? or raise ArgumentError
     ... end

which seems pretty annoying-for-no-reason.

I was thinking of just adding some mandatory parameters after the parameter lists that are allowed today (of course before a block parameter). So the following would be allowed:

   def baz(a, b=nil, c)
   def baz(a, *b, c, d)
   def baz(a, b=nil, *c, d, e)

but not the following:

   def baz(a, *b, c=nil)
   def baz(a, b=nil, c, d=nil, e)

Of course, this is only my personal opinion. If you can think of other use cases where you'd like to have even more flexibility, please go ahead. In any case I'm looking forward to the RCR.

Regards,
Pit

Hi Eric,

I thought you were going to submit an RCR for this (not
too related to the original RCR topic of this thread - nil
for unassigned keys - of a hash).

I know, I'm sorry. As I said earlier in this thread,

That reminds me, I promised I would write an RCR for
allowing trailing argument after the splatting one.

It was said that optional arguments should be
allowed before the splat, but not after it.

Eric, what is your opinion on this? Do you think these

   def foo(a, b=nil, c, d=nil, e) ... end
   def bar(a, b=nil, *c, d=nil, e) ... end
   def baz(*a, b=nil, c=nil) ... end

should all be allowed?

If so, then which variable, b or c, should be bound when
invoking baz with one argument? The obvious answer is b,
but you could argue that c makes more sense, since c is
farthest from the splat.

Here is what was said in the thread "Re: iterators and
block arguments" (which covers your concern above):

I don't think it quite covers my concern, as it only deals
with the case of mandatory arguments after the splat.

···

--
Daniel Brockman <daniel@brockman.se>

    So really, we all have to ask ourselves:
    Am I waiting for RMS to do this? --TTN.

Hi Eric,

> I thought you were going to submit an RCR for this (not
> too related to the original RCR topic of this thread - nil
> for unassigned keys - of a hash).

I know, I'm sorry. As I said earlier in this thread,

> That reminds me, I promised I would write an RCR for
> allowing trailing argument after the splatting one.

> It was said that optional arguments should be
> allowed before the splat, but not after it.

Eric, what is your opinion on this? Do you think these

   def foo(a, b=nil, c, d=nil, e) ... end

don't like it - too ambiguous. Does b or d get assigned when
you have 4 args?

   def bar(a, b=nil, *c, d=nil, e) ... end

also don't like it - too ambiguous. Does b or d get assigned
when you have 3 args?

   def baz(*a, b=nil, c=nil) ... end

maybe. you could handle this if optional args have priority
over splats.

should all be allowed?

If so, then which variable, b or c, should be bound when
invoking baz with one argument? The obvious answer is b,
but you could argue that c makes more sense, since c is
farthest from the splat.

I was thinking c to be symmetrical with optionals coming before
the splat.

I think the meat of this RCR should handle these cases:

def f(a, b, c=c0, d=d0, y, z) ... end

def f(a, b, *m, y, z) ... end

def f(a, b, c=c0, d=d0, *m, y, z) ... end

I think these are pretty clear: y and z get the last 2 args and
the rest of the args are handled as they are now. I think this
would be a bit more controversial:

def f(a, b, *m, w=w0, x=x0, y, z) ... end

In this case, after grabbing a, b, y, and z from the args, x
would have priority over w and w over m. That would be
symmetrical with optional args coming before the splat, but
confusing. Especially when you might be confused as to whether
w or x should have priority (as you discussed above).

What case do you have where you want optional args after the
splat?

···

--- Daniel Brockman <daniel@brockman.se> wrote:

__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around

Eric Mahurin <eric_mahurin@yahoo.com> writes:

Eric, what is your opinion on this? Do you think these
should all be allowed?

   def foo(a, b=nil, c, d=nil, e) ... end

don't like it - too ambiguous. Does b or d get assigned
when you have 4 args?

I'd say it's obvious that b gets assigned, since the above
case is very much alike this one:

   def foo(a, b=nil, d=nil) ... end

It doesn't make sense to prioritize d over b.

Anyway, it is difficult to see the usefulness of this
without an actual example.

   def bar(a, b=nil, *c, d=nil, e) ... end

also don't like it - too ambiguous. Does b or d get
assigned when you have 3 args?

I agree, this one is ambiguous. But I think the obvious
choice is to prioritize the arguments left of the splat.

   def baz(*a, b=nil, c=nil) ... end

maybe. you could handle this if optional args have
priority over splats.

I think that's intuitive.

If so, then which variable, b or c, should be bound when
invoking baz with one argument? The obvious answer is b,
but you could argue that c makes more sense, since c is
farthest from the splat.

I was thinking c to be symmetrical with optionals coming
before the splat.

I tend to agree.

I think the meat of this RCR should handle these cases:

def f(a, b, c=c0, d=d0, y, z) ... end

def f(a, b, *m, y, z) ... end

def f(a, b, c=c0, d=d0, *m, y, z) ... end

I think these are pretty clear: y and z get the last 2
args and the rest of the args are handled as they are now.

Yes, these cases are not only crystal-clear; they are also
the ones that seem the most useful.

I think this would be a bit more controversial:

def f(a, b, *m, w=w0, x=x0, y, z) ... end

In this case, after grabbing a, b, y, and z from the args,
x would have priority over w and w over m. That would be
symmetrical with optional args coming before the splat,
but confusing. Especially when you might be confused as
to whether w or x should have priority (as you discussed
above).

I agree, these cases are confusing. Maybe if we had an
actual example of two optional arguments after the splat
occuring naturally, it would become easier to grasp.

What case do you have where you want optional args after
the splat?

This came up earlier in the thread:

   def foo(*args)
     options = args.last.class == Hash ? args.pop : {}
     args.each { |x| options = true }
     ...
   end

For a while I thought that could be expressed like so,

   def foo(*flags, options={})
     flags.each { |x| options = true }
     ...
   end

but I later realized that was not a good example, because
this version cannot handle cases like ‘foo :bar, :baz’.
It would have to look like this,

   def foo(*flags, options={})
     unless options.class == Hash
       flags.push options ; options = {} end
     flags.each { |x| options = true }
     ...
   end

which is actually worse than the original version.

So I don't have any example of a case where optional
arguments after the splat is useful — maybe there are none.
If someone can think of a case, please speak up. I will
think about it for a while.

···

--
Daniel Brockman <daniel@brockman.se>

    So really, we all have to ask ourselves:
    Am I waiting for RMS to do this? --TTN.