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!
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.
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.
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.
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.
David Black is your grandmother??!!!
Shh!
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.
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.