From: Dan Doel Date: 2004-05-05T09:16:30+09:00 Subject: Re: Why no Proc##[]=() ? Why no Proc##replace() ? On Tuesday 04 May 2004 2:42 am, Jean-Hugues ROBERT wrote: > Lets try (using Proc instead of Method, we both agree I think that a Proc > can act as an anonymous method). > > # Make it so that a Proc can be a valid lvalue: > class Proc; def []=(*a) self[*a] end end > > def trace_array( an_array, msg ) > proc { |*args| > if args.length() == 1 then > p "#{msg}: Read access at pos #{args[0]}" > an_array[args[0]] > elsif args.length() == 2 then > p "#{msg}: Write access at pos #{args[0}" > an_array[args[0]] = args[1] > else > p "#{msg}: Weird access." > an_array[*args[0...-1]] = args[-1] > end > } > end > > def buggy_meth() > a = [1,2,3] > a = trace_array( a, "a in buggy_meth()") if $Debug > ... use a ... > end Ah. Well, you have an example where #[]= makes sense for a Proc. However, you have specifically written your Proc to work differently based on the number of arguments so that it does different things for [a] and [a] = b. Most Proc objects aren't this way. This is more of a "Socrates is a man => All men are Socrates" situation. Just because you have examples where #[]= makes sense doesn't mean it makes sense in general. All Arrays, Strings and Hashes have indexed elements. Not all Proc objects pretend to. So really, you should only implement #[]= for Procs you specifically build to work this way (which you can do in Ruby). However, this doesn't mean it should be a general property of all Procs. > In this example you can substitute a Proc where an Array was expected. > The Proc is invoked both for read and write accesses to the Array. > It outputs a msg and then performs the required operation on the Array. > > This is a simple debugging tool made possible thanks to an additional > level of indirection. Incidentally, the Proc won't respond to all the methods of Array. It's probably better to redefine the #[] and #[]= methods of that specific Array to do the logging, rather than wrap it in a proc that pretends to be an Array for two methods. So the Proc method isn't necessarily the best way to do it. > > a[i, j, k] = l, m, n #=> becomes a.call(i, j, k, [l, m, n]) > > Its actually # => becomes a.call( i, j, k, l). :) No, it does what I said. Try it out. > We both agree I think that o.x = y is a nicer syntax then o.x( y). > Only I think that p[] = y is a nicer syntax then p[y] if proc were an > accessor. But p isn't always an accessor. Your #[]= proposal is similar to saying that because we can call p.a, we should always be able to call p.a = b. There are many cases where p.a = b doesn't make sense, whether it's because the attribute is read only, or because #a isn't an attribute at all. For example, if you can call: foo.compute_interest Does it make sense to say: foo.compute_interest = 6 ? Now, what if you do m = foo.method(:compute_interest) Does m[] = 6 make sense? If it doesn't make sense for all cases, you shouldn't automatically define it for all cases. > Another one: > If ptr = Pointer.new(...) then p[] = x is nicer syntax (to me) than p.set( > x) because > to me it reads "the content of p is assigned the value of x". But a Pointer is not a Proc. Pointers have content that you can read/write. The 'content' of a Proc is some code and the context that code is from. You can't write to it (directly). You can only execute it and get the return value. > Back to block: b[] = x, to me, reads as "the content of b is assigned the > value of x". > What that means exactly depends on the semantic of the block b. But b[] isn't "the content of b" for Proc objects. It's "call b." They're different conventions. In C++, I can do cout << "Hello."; << also works on ints. Does this make sense? a = 5 << "Hello"; Just because it's the same symbols (<< or []) doesn't mean it does the same thing. [] for Array means something different than [] for Proc. It's handy that they can be used interchangably in some circumstances, but that doesn't mean they're interchangeable in all circumstances. Just because Arrays have [] doesn't mean I expect them to define #call like a Proc. So just because Procs have [] doesn't mean they should have []= like an array. > My conclusion is: > When x[ii] means "content of x" and x[ii]= y means "content of x is > assigned the value of y", > it makes sense that x can be anything, a Method or a Block included, > because I should > not care about that, it is up to the implementation to decide. > As a consequence it makes sense to define Proc##[]= as much as Proc##[] and > def []=(*a) self[*a] end makes sense as a default implementation. x[ii] doesn't always mean "content of x." Ruby doesn't define what [] means for every object. I can make it mean whatever I want: class Foo def [](x) x + 5 end end Now, Foo.new[x] just means "x + 5". []= here has no meaning. There would be no assignment. Does this make sense: Foo.new[] = x <==> x + 5 It certainly doesn't make sense to me. It looks like assignment, but it's just adding 5 to x. Now consider the following block: lambda { |x| x + 5 } This is the same as Foo above. > If c is_a? Lvalue then the lvalue object that c refers to (& which can be > any lvalue, > a variable included), becomes a new Foo (which means that it now refers to > a Foo: > c now refers to the same something but that something now refers to the new > Foo). So the interpreter has to do runtime checks to see if a variable has an object of type Lvalue and then does special things if it is? What if I want to make my own class that has special assignment characteristics, but don't want to inherit from Lvalue? I can't. > However, this does not mean that all Ruby variables would have to be > different from what they are today (whatever the name you use to describe > what they are today). Only variables that holds a reference to a Lvalue > object would have to be treated differently than the "normal" variables. > That's because the Ruby interpretor would have to invoke some .getter() or > .setter() method of the Lvalue instead of using the variable's content > directly (or, to rephrase more formally, xxx instead of directly using the > reference to some object that the variable holds). How do you determine at compile time whether a = b means "a = b" or "a.setter(b.getter)"? The only answer I can see is, "it's always 'a.=(b)'." Which means by-value assignment. The only way you could keep Ruby's existing assignment semantics, is if 'a.=(b)' was by default 'a.become(b)'. > I am not proposing such a radical change at all. I would rather go forward > than backward :-) What I am proposing is an additional tool, by the way of > an additional level of indirection. When the programmer need that tool it > has to be explicit and she/he would create a Lvalue object using some > explicit syntax: > b = "toto" > c = ref b # *explicit* > c = "titi" > p b # => "titi" > c is like an alias for b. So is c a different type of variable than b? Does this mean we have regular type variables and reference type variables? Are reference type variables only able to have their references set at time of definition? How does this work: a = "toto" b = "frodo" c = ref b c = ref a At the end, what is b? Is b a reference to a? Is b still "frodo"? If you pick one, what if I want the other? Also, what about: a = "toto" c = "frodo" c = ref a How does the compiler know which type of variable c is, because it holds both regular and reference types at various times? > The implementation of Lvalue that I am propotyping does not use #become > (#become BTW is not yet fully available I believe, but that is not the > reason). > > b = "toto" > p b.object_id() # 123 > c = ref b > c = "titi" > p b.object_id() # 456 > If I were to use #become, b.object_id() would stay the same. It's not the > case and must not be. > > As a matter of fact, I am very unsure that a Lvalue class could be > implemented at all using #become. Perhaps #become was the wrong way of putting it. Here's what I'm saying. By distinguishing between regular and reference variables or whatever, what you're saying is that "A variable represents a chunk of memory." Currently, Ruby says, "A variable is a reference to an object." For your proposal to work, 'a = b' for reference variables would mean, "copy b's memory into the place pointed by a." Currently, it's "make a point to the same place as b." In your proposal, 'a = b' for regular variables (and there is a distinction) would mean "copy b's memory into a's memory." I guess what you want is to keep "every variable is a reference" and you want reference references. Or something like that. You've already built that with your pointer class, it just isn't as transparent as regular assignment. But in my mind, that's okay, because what you want isn't useful/the correct way to do things very often. If we had what you envision, we could have people writing: foo(a, ref b) instead of b = foo(a) Which is bad. It's like C. The only reason you write C code like that is to return error codes while still passing out information, or to pass out multiple values. We have exceptions for error conditions, and we can easily return out multiple values. So variable references are, in fact, _the incorrect Ruby-way to do things_ for the two biggest cases of their use in C. > Then you don't mind that much that "In Ruby everything is an object, but > variables and > ... and ...". > I would prefer "In Ruby everything is an object". Introspection is a great > tool, the more, the better. To me, variables in Ruby are imaginary. They only help me, and don't exist as far as the objects/interpreter are concerned. The only way to get an object is by it's object id, and a variable holds an id for my convenience. Saying 'a = b' copies the id from b to a. Then 'a.foo' means the interpreter actually looks up the object with id stored in a, and calls its method foo. Of course, it's more efficient than that, but it works from a conceptual point of view. Ruby programs are collections of interating objects, not collections of interacting variables. Variables just make things easier for me to read when I'm telling the objects what to do. Whether or not an object realizes that it's method contains a local that I called "bar" doesn't matter as long as the method returns the right result. It could rename my variable to "baz" or "#0526ABFC" for all I care. I doubt reference variables would be used for introspection. You can already get and set instance variables by a method. What is the purpose of knowing that a method has a local variable called "x"? Being able to assign to a variable in one place and have it affect a variable in an entirely different would, in my opinion, be more often confusing than useful. Your logic methods are the only good example I know of, and you can implement them in pure Ruby as long as you don't mind using explicit dereferencing and some eval evil. I don't think they would be generally useful, because there are better ways to do most of what they let you accomplish. Cheers. - Dan