The use case!

27 views
Skip to first unread message

David A. Black

unread,
Nov 23, 2008, 12:46:36 PM11/23/08
to ruby-...@googlegroups.com
Hi all --

I thought I'd tie up a loose end by letting you know that Koichi
answered a question from me on ruby-core and now I know that you can
do this in 1.9:

class C
def []=(a,b=1,c=2,d) # or whatever; a and d are the main ones
p [a,b,c,d]
end
end

c = C.new
c[0] = 3 # [0,1,2,3]
c[0,"b"] = 3 # [0,"b",2,3]

etc. In other words, the point of putting a required argument on the
right is that it can be used in conjunction with defining the []=
method, such that the right-hand parameter will be bound to the
right-hand side of the []= call.

OK, that's potentially pretty cool :-)

Nice to see y'all. I'll be in and out of London a lot in the next few
weeks, so anyone who wants to try to meet up, please get in touch.


David

--
Rails training from David A. Black and Ruby Power and Light:
Intro to Ruby on Rails January 12-15 Fort Lauderdale, FL
Advancing with Rails January 19-22 Fort Lauderdale, FL *
* Co-taught with Patrick Ewing!
See http://www.rubypal.com for details and updates!

Tom Ward

unread,
Nov 24, 2008, 6:04:04 AM11/24/08
to ruby-...@googlegroups.com
Thanks for doing this David. It makes a bit more sense, though it's
still pretty horrible.

Tom

2008/11/23 David A. Black <dbl...@rubypal.com>:

David A. Black

unread,
Nov 24, 2008, 8:22:37 AM11/24/08
to ruby-...@googlegroups.com, t...@popdog.net
Hi Tom --

On Mon, 24 Nov 2008, Tom Ward wrote:

>
> Thanks for doing this David. It makes a bit more sense, though it's
> still pretty horrible.

Yes, the problem still exists of arguments moving around. In fact, I
should add here that Tom (whom I'm addressing, but also publicly
thanking :-) pointed me back to the example that I didn't succeed in
capturing on my slide, namely the one where arguments change:

def m(a,b='b',c); p [a,b,c]; end
m(1,2) # [1,'b',2] c is 2
m(1,2,3) # [1,2,3] b is 2!

You'd have to go out of your way to do it, but it's still a pitfall.
(That's the one I was trying to illustrate with my def
register(first_name...) example, which didn't in fact illustrate it.)


David

Piers Cawley

unread,
Nov 24, 2008, 1:40:04 PM11/24/08
to ruby-...@googlegroups.com
On Mon, Nov 24, 2008 at 11:04 AM, Tom Ward <t...@popdog.net> wrote:
>
> Thanks for doing this David. It makes a bit more sense, though it's
> still pretty horrible.

Substantially less horrible than implementing []= with a signature like:

class Array
def []=(index, *args)
value = args.pop
length = args[0] || 1
raise ArgumentError, "Wrong number of arguments"
...
end
end

I got sick of unrolling @_ in every damned function when I was
programming in Perl. It was one of the main reasons I made the switch
to Ruby. Destructuring argument lists are pretty cool in this
direction as well. Personally, I'd like destructuring to work on
hashes as well - the hard part there is coming up with good syntax
though.

def something(:key => a, opts = {})
[a, opts]
end

something(:key => 99, :other => 100) => [99, {:other => 100}]

is one approach, but I'm not exactly loving it. However,

def something(opts = {})
if opts.has_key? :key
raise ArgumentError, "Hash key :key required!"
else
a = opts.delete :key
end
[a, opts]
end

isn't exactly loveable either. The more stuff the runtime can do for
me, the happier I am.

David A. Black

unread,
Nov 24, 2008, 2:42:23 PM11/24/08
to ruby-...@googlegroups.com
Hi --

On Mon, 24 Nov 2008, Piers Cawley wrote:

>
> On Mon, Nov 24, 2008 at 11:04 AM, Tom Ward <t...@popdog.net> wrote:
>>
>> Thanks for doing this David. It makes a bit more sense, though it's
>> still pretty horrible.
>
> Substantially less horrible than implementing []= with a signature like:
>
> class Array
> def []=(index, *args)
> value = args.pop
> length = args[0] || 1
> raise ArgumentError, "Wrong number of arguments"
> ...
> end
> end

I was chatting with Yehuda Katz about this on IRC yesterday, and one
thing I said was "i could imagine that implementing something like
Array#[]= in ruby would be easier if you knew where the rhs value(s)
were going to land". Great minds, and all that :-)

My problem with it is that it seems a bit like a single use case
dictating syntax. I wish that (a) it were more broadly useful (which
maybe it will prove to be) and (b) it did not introduce pitfalls at
the same time that it solved one particular problem.

pdcawley

unread,
Nov 24, 2008, 5:01:10 PM11/24/08
to Ruby Manor
On Nov 24, 7:42 pm, "David A. Black" <dbl...@rubypal.com> wrote:
> On Mon, 24 Nov 2008, Piers Cawley wrote:
>
> > On Mon, Nov 24, 2008 at 11:04 AM, Tom Ward <t...@popdog.net> wrote:
>
> >> Thanks for doing this David.  It makes a bit more sense, though it's
> >> still pretty horrible.
>
> > Substantially less horrible than implementing []= with a signature like:
>
> > class Array
> > def []=(index, *args)
> >   value = args.pop
> >   length = args[0] || 1
> >   raise ArgumentError, "Wrong number of arguments"
> >   ...
> > end
> > end
>
> I was chatting with Yehuda Katz about this on IRC yesterday, and one
> thing I said was "i could imagine that implementing something like
> Array#[]= in ruby would be easier if you knew where the rhs value(s)
> were going to land". Great minds, and all that :-)
>
> My problem with it is that it seems a bit like a single use case
> dictating syntax. I wish that (a) it were more broadly useful (which
> maybe it will prove to be) and (b) it did not introduce pitfalls at
> the same time that it solved one particular problem.

I'm still not seeing the pitfalls to be honest. The example you give
is only confusing because it's a mickey mouse example. In it's proper
home, who cares. The style rules of thumb for using required
parameters at the end of a method signature are:

1. Only use them on []= (I was going to suggest other assignment
methods, but it seems the parser barfs on them)
2. If you have more than one required parameter after your optionals,
I will find and kill you.
3. If you interleave optionals and required 'insane(a, b=nil, c,
d=nil, e)' we will _all_ find and kill you.

Now, maybe someone's going to come along and find a compelling use for
this sort of thing that'll break those rules, but I'd be very
surprised. If they _do_, then I'm betting it's being done as part of a
seriously hairy pidgin.

David A. Black

unread,
Nov 25, 2008, 2:30:18 AM11/25/08
to Ruby Manor
Hi --

That's the main pitfall: a general syntactic construct that's actually
just accomodating one use case. I don't like the *args.pop thing
either, but it strikes me as kind of a loose fit to have a pretty
radical change in argument semantics solely for the benefit of people
who happen to need to define []=, especially a change that could be
confusing and could open the door to problems.

It's always a bit of a danger sign when there's a construct of which I
fully expect to say, in writing about and teaching it: don't ever do
this. It's not even like eval or sending private methods or whatever
(that's more like: don't make a habit of this, but do understand it
because you might use it and other people's code uses it).

That said, I doubt it will be a huge problem, though a serious pain to
try to explain.

Piers Cawley

unread,
Nov 25, 2008, 3:26:14 AM11/25/08
to ruby-...@googlegroups.com

Bah! Teachers! Just don't explain it.

Seriously, now you understand the use case, it should be a good deal
easier to explain - just explain it in the context of implementing
[]=. You don't have to introduce the full argument syntax straight
away after all. If it offends your honesty, just remark that your
initial explanation isn't quite true and you'll cover the exceptions
and wrinkles later. Heck, it works well enough in The Structure and
Interpretation of Computer Programs - look at the way the
computational model is slowly refined from a simple minded
substitution model all the way up to the full explicity control
evaluator.

Feel free to stop me at any point by muttering "Pointy end grandma",
or using some other means of telling me I'm teaching my grandmother to
suck eggs.

Sean O'Halpin

unread,
Nov 25, 2008, 3:53:48 AM11/25/08
to ruby-...@googlegroups.com
On Tue, Nov 25, 2008 at 8:26 AM, Piers Cawley <pdca...@gmail.com> wrote:
> Feel free to stop me at any point by muttering "Pointy end grandma",
> or using some other means of telling me I'm teaching my grandmother to
> suck eggs.

David Black is your grandmother??!!!

Piers Cawley

unread,
Nov 25, 2008, 5:08:26 AM11/25/08
to ruby-...@googlegroups.com

Shh!

David A. Black

unread,
Nov 26, 2008, 4:24:34 AM11/26/08
to ruby-...@googlegroups.com
Hi --

I'm pretty solid on the "teaching and writing consist of more than a
brain-dump" thing :-) The issue in this particular case, at least with
regard to my book, is the feature itself, and the decision about how
to handle it is more at the level of what tone to take, whether to
have a "Warning" callout or not, that kind of thing. It's not really a
big deal in the overall course of things; expository writing involves
hundereds of such decisions, but this one just happened to get the
spotlight.

David Beckwith

unread,
Nov 26, 2008, 4:49:05 AM11/26/08
to ruby-...@googlegroups.com
Hi David,

I just want to say, that I read your book Ruby for Rails twice, and
I really enjoyed it. I always recommend people to read your book
first, if they want to learn Ruby on Rails. That said, this totally
confuses me:

class C
def []=(a,b=1,c=2,d) # or whatever; a and d are the main ones
p [a,b,c,d]
end
end

c = C.new
c[0] = 3 # [0,1,2,3]
c[0,"b"] = 3 # [0,"b",2,3]

I think the thing that confuses me is that

c[0] = 3 makes me think that there is an array that looks like this: [3]

By adding the two default values, that's going to make me think of
this: [3, 1, 2] .... then ArgumentError cuz the 2nd argument wasn't
supplied. or possibly [3, 1, 2, 3]. But I'm filling in arguments from
left to right. Is it because Matz is from Japan that he is filling
out arguments from right to left?

Finally, c[0, "b"] = 3 makes me think of a hash { 0=>3, "b" => 3 }

So, in conclusion, wtf.

Thank you,
David Beckwith :)

David A. Black

unread,
Nov 27, 2008, 4:30:19 AM11/27/08
to ruby-...@googlegroups.com
Hi David --

On Wed, 26 Nov 2008, David Beckwith wrote:

>
> Hi David,
>
> I just want to say, that I read your book Ruby for Rails twice, and
> I really enjoyed it. I always recommend people to read your book
> first, if they want to learn Ruby on Rails.

Thanks! And keep an eye out for the next incarnation of it, which is a
much revised and expanded, Ruby-only version called "The Well-Grounded
Rubyist."

(Aside to the Guvner: I didn't *try* to use the list to promote my
book. It just sort of happened! :-)

> That said, this totally
> confuses me:
>
> class C
> def []=(a,b=1,c=2,d) # or whatever; a and d are the main ones
> p [a,b,c,d]
> end
> end
>
> c = C.new
> c[0] = 3 # [0,1,2,3]
> c[0,"b"] = 3 # [0,"b",2,3]
>
> I think the thing that confuses me is that
>
> c[0] = 3 makes me think that there is an array that looks like this: [3]
>
> By adding the two default values, that's going to make me think of
> this: [3, 1, 2] .... then ArgumentError cuz the 2nd argument wasn't
> supplied. or possibly [3, 1, 2, 3]. But I'm filling in arguments from
> left to right. Is it because Matz is from Japan that he is filling
> out arguments from right to left?
>
> Finally, c[0, "b"] = 3 makes me think of a hash { 0=>3, "b" => 3 }
>
> So, in conclusion, wtf.

Don't think of arrays and hashes. We're only dealing with an instance
of the class C, and therefore C's definition of []=. []= is just a
method. In all cases (including arrays and hashes), this:

a[1] = 2

is just syntactic sugar for this:

a.[]=(1,2) # call []= with two arguments

The best way to delve into the 1.9-style parameter semantics is, I
think, to look at the problem that's being addressed by this syntax.
Run this code in 1.8:

class C
def []=(required,*rest)
puts "Required arg: #{required.inspect}"
puts "Remaining args: #{rest.inspect}"
end
end

c = C.new
c[1] = 10 # same as c.[]=(1,10)
c[1,2] = 10 # same as c.[]=(1,2,10)

The first call gives you:

Required arg: 1
Remaining args: [10]

and the second gives you:

Required arg: 1
Remaining args: [2, 10]

The problem is that you probably want that 10, which is on the
right-hand side when you use the assignment-like calling syntax,
always to land in the same variable. You can bring that about with:

rhs_value = rest.pop

and then rest will either be empty or will contain more values (2, in
the second of the above examples). You then have to figure that out,
and assign the remaining values to other variables:

optional_arg = rest[0]

In other words, the parameter and argument semantics are not really
helping you reach your goal, which is to differentiate cleanly between
the values inside the brackets and the values to the right of the
equal sign.

Now, in 1.9 you can do this:

def []=(required, optional='maybe', rhs)

When you call this like this:

c[1] = 10 # same as c.[]=(1,10)

the 1 get bound to 'required', and the 10 gets bound to 'rhs'. That's
because 1.9 will bind required parameters at both ends of the
parameter list, before it does anything with optional ones in the
middle.

Which means that if you do this:

c[1,2] = 10 # same as c.[]=(1,2,10)

'required' is 1 again, and 'rhs' is 10 again. That leaves 2 to be
bound to 'optional'.

What's happening, in effect, is that the right-hand side of the
assignment-style method call (10, in the examples) is being
absolutely, instead of relatively, bound. So instead of ending up as
the first, or maybe second, or third... element of a *rest array, it
just gets bound to whatever the last parameter is (rhs). Thus you
don't have to resort to examining an array of "all remaining
arguments" to figure out which one(s) should be bound to what.

The reason I'm not in love with this, by the way, is that it seems to
me to be the tail wagging the dog. The whole a[b] = c thing is just
syntactic sugar for a.[](b,c), and I'm slightly uneasy with the
semantics of method parameter binding being altered to align with
syntactic sugar. On the other hand, the reality is that a[b] = c is
much more real to most people than a.[]=(b,c), so my reservations may
be misguided.

David Beckwith

unread,
Nov 27, 2008, 6:34:47 AM11/27/08
to Ruby Manor
Brilliant explanation. I get it now. Thank you!!

David :)

James Adam

unread,
Nov 27, 2008, 11:48:49 AM11/27/08
to ruby-...@googlegroups.com
This has definitely 'clicked' for me now too. It's just to "help" when
you want to easily get to whatever appears after the '=' sign in one
of these array-esque methods (normally what appears to be the "value
being assigned"):

class MultidimensionalBitmap
def []=(x, y, *other_coordinates, value_to_store)
# the implementation doesn't matter
end
end

space = MultidimensionalBitmap.new

space[0,0] = "home" # value_to_store == "home"
space[10,5,6] = "far from home in 3D space" # value_to_store ==
"far from home in 3D space"


Hopefully I've understood. Thanks for spending the time clarifying
this, David!

James
Reply all
Reply to author
Forward
0 new messages