[RCR] nil for unassigned keys

S

Simon Strandgaard

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

def test(hash=3D{})
=09p hash
end

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


Is this too far out?
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: [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}

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

matz.
 
S

Simon Strandgaard

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

def test(arg1, arg2, hash=3D{})
=09puts "#{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=3D>nil}
test(1, 2, 3, 4) # 1 2 {3=3D>nil, 4=3D>nil}
test(1, 2, 3, 4=3D>5) # 1 2 {3=3D>nil, 4=3D>5}
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: [RCR] nil for unassigned keys"

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

Simon Strandgaard

=20
|Maybe I don't understand the mandatory argument thing?
|But can't you just count them somehow?
=20
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
=20
test(1, 2, 42) # [1,2,42]
test(1, 2, 3, 4) # [1,2,3,4]
test(1, 2, 3, 4=3D>5) # [1,2,3,{4=3D>5}]
=20
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.

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

Ara.T.Howard

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)
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
--
===============================================================================
| 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
===============================================================================
 
S

Simon Strandgaard

=20
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 y= ou
[snip]
which at least allows
test(h('x', 'z', 'y' =3D> 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=3D>true)


it could be a little nicer with

"aoeu".to_a:)bytes)
 
D

Daniel Brockman

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

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

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

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

test :x =3D> 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=3D{})
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=3D>nil}
test(1, 2, 3, 4) # 1 2 {3=3D>nil, 4=3D>nil}
test(1, 2, 3, 4=3D>5) # 1 2 {3=3D>nil, 4=3D>5}

You can always do this:

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

def test(arg1, arg2, *args)
options =3D 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=3Dnil, *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=3D{})

The above is unambiguous: we must always try to assign one
argument to =E2=80=98options=E2=80=99 =E2=80=94 anything else wouldn't ma=
ke sense.
It only becomes a problem when you have optional arguments
both before *and* after the splat:

def test3(arg1, arg2, arg3=3Dnil, *flags, o=3D{})

Obviously, when =E2=80=98test3=E2=80=99 is called with four arguments, we
must assign the third to =E2=80=98arg3=E2=80=99 and the fourth to =E2=80=98=
options=E2=80=99,
but what if only three arguments are given =E2=80=94 does the last
one go to =E2=80=98arg3=E2=80=99 or to =E2=80=98options=E2=80=99?

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=3Dnil, *c, d=3Dnil, e) ... end

foo 1 # ArgumentError
foo 1, 2 # a=3D1, b=3Dnil, c=3D[], d=3Dnil, e=3D2
foo 1, 2, 3 # a=3D1, b=3D2, c=3D[], d=3Dnil, e=3D3
foo 1, 2, 3, 4 # a=3D1, b=3D2, c=3D[], d=3D3, e=3D4
foo 1, 2, 3, 4, 5 # a=3D1, b=3D2, c=3D[3], d=3D4, e=3D5

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

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

foo 1 # ArgumentError
foo 1, 2 # ArgumentError
foo 1, 2, 3 # a=3D1, b=3Dnil, c=3D2, d=3Dnil, e=3D3
foo 1, 2, 3, 4 # a=3D1, b=3D2, c=3D3, d=3Dnil, e=3D4
foo 1, 2, 3, 4, 5 # a=3D1, b=3D2, c=3D3, d=3D4, e=3D5

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

def []=3D *coordinates, value

--=20
Daniel Brockman <[email protected]>

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

Pit Capitain

Daniel said:
...
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
 
A

Ara.T.Howard

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)

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

"aoeu".to_a :bytes=>1

cheers.

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

Daniel Brockman

Hi Pit,

Thank you for your comments.
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=3Dnil, 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 =E2=80=9Conly if
there's a splat argument somewhere!=E2=80=9D is pretty arbitrary.

If you wanted the semantics of this,

def baz(a, b=3Dnil, c) ... end

you'd have to write it like this,

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

which seems pretty annoying-for-no-reason.

--=20
Daniel Brockman <[email protected]>

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

Eric Mahurin

--- Daniel Brockman said:
Thank you for your comments.
=20
Then what about this?
=20
def baz(a, b=3Dnil, c) ... end
=20
Isn't that just as easy to grasp as this?
=20
def baz(a, *b, c) ... end
=20
=20
You may be right. But I think restricting this to =E2=80=9Conly if
there's a splat argument somewhere!=E2=80=9D is pretty arbitrary.
=20
If you wanted the semantics of this,
=20
def baz(a, b=3Dnil, c) ... end
=20
you'd have to write it like this,
=20
def baz(a, *dummy, b=3Dnil, c)
dummy.empty? or raise ArgumentError
... end
=20
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):

--- Eric Mahurin said:
--- Daniel Brockman said:
Maybe this syntax could be adopted?
=20
def foo a, b, *args, c, d; ... end
=20
I want that too. It would be nice for the []=3D method where
the
value is always the last argument. Or any method where you
want optional arguments first or in the middle.
=20
In general, this:
=20
def foo a, b, c=3Dx, d=3Dy, *other, e, f
...
end
=20
should be equivalent to:
=20
def foo a, b, *remaining
e,f =3D remaining.slice!(-2,2)
c =3D remaining.empty? ? x : remaining.shift
d =3D remaining.empty? ? y : remaining.shift
other =3D remaining
...
end
=20
Want to submit an RCR for this? I've already done my fair
share.



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

Daniel Brockman

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,

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

Myself> It was said that optional arguments should be
Myself> 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.
 
N

Nikolai Weibull

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
 
E

Eric Mahurin

--- Daniel Brockman said:
Hi Eric,
=20
=20
I know, I'm sorry. As I said earlier in this thread,
=20
Myself> That reminds me, I promised I would write an RCR for
Myself> allowing trailing argument after the splatting one.
=20
Myself> It was said that optional arguments should be
Myself> allowed before the splat, but not after it.
=20
Eric, what is your opinion on this? Do you think these
=20
def foo(a, b=3Dnil, c, d=3Dnil, e) ... end

don't like it - too ambiguous. Does b or d get assigned when
you have 4 args?
def bar(a, b=3Dnil, *c, d=3Dnil, e) ... end

also don't like it - too ambiguous. Does b or d get assigned
when you have 3 args?
def baz(*a, b=3Dnil, c=3Dnil) ... end

maybe. you could handle this if optional args have priority
over splats.
should all be allowed?
=20
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=3Dc0, d=3Dd0, y, z) ... end

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

def f(a, b, c=3Dc0, d=3Dd0, *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=3Dw0, x=3Dx0, 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?


__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around=20
http://mail.yahoo.com=20
 
P

Pit Capitain

Hi Daniel.
Then what about this?
=20
def baz(a, b=3Dnil, c) ... end
=20
Isn't that just as easy to grasp as this?
=20
def baz(a, *b, c) ... end

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

def bar(a, b=3Dnil, c, d=3Dnil, e) ... end
I think restricting this to =E2=80=9Conly if
there's a splat argument somewhere!=E2=80=9D is pretty arbitrary.
=20
If you wanted the semantics of this,
=20
def baz(a, b=3Dnil, c) ... end
=20
you'd have to write it like this,
=20
def baz(a, *dummy, b=3Dnil, c)
dummy.empty? or raise ArgumentError
... end
=20
which seems pretty annoying-for-no-reason.

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

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

but not the following:

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

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

Regards,
Pit
 
D

Daniel Brockman

Eric Mahurin said:
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=3Dnil, d=3Dnil) ... 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.
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.
maybe. you could handle this if optional args have
priority over splats.

I think that's intuitive.
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=3Dc0, d=3Dd0, y, z) ... end

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

def f(a, b, c=3Dc0, d=3Dd0, *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=3Dw0, x=3Dx0, 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 =3D args.last.class =3D=3D Hash ? args.pop : {}
args.each { |x| options[x] =3D true }
...
end

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

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

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

def foo(*flags, options=3D{})
unless options.class =3D=3D Hash
flags.push options ; options =3D {} end
flags.each { |x| options[x] =3D 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 =E2=80=94 maybe there are none.
If someone can think of a case, please speak up. I will
think about it for a while.

--=20
Daniel Brockman <[email protected]>

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

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

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top