From: Dan Doel Date: 2004-05-04T00:53:28+09:00 Subject: Re: Why no Proc##[]=() ? Why no Proc##replace() ? On Monday 03 May 2004 7:54 am, Jean-Hugues ROBERT wrote: > Sometimes, a Proc is sometimes like an anonymous method, right ? > If assignment does not make sense for proc p, > then is does not make sense for method m. > then m.[]=( 1, x) does not make any more sense than p.[]=( 1, x) > Then m[1] = x does not make sense either > Then it is an embarrassing situation because a[1] = x does not make > sense... So, it has to make sense for method, and it should make sense for > proc too. Am I missing something ? Right. If m is a Method object, then m[1] = x doesn't make sense either. m[1] = x only makes sense to me if m is an Array or a Hash or a String or something where it makes sense to talk about the Nth element of m. Proc and Method objects are not such types, in my opinion. > That is true, it feels like treating assignment as an operation on object. > But it not true that Ruby treats assignment as an operation on variables, > it does so only for scalar object. For non scalars, arrays, hash, etc, > assignment is by method call (.[]=() on the non scalar object). We look at this in different ways. Let me try to explain my point of view more clearly. o.a = b is to me a nicer way of saying o.set_a(b) Which is what you'd do in Java. In Java, you can have lvalues of the form o.a if a is a public variable o. This is _never_ the case in Ruby, because there are only private instance variables. However, Matz recognizes that it looks nicer if you can make attribute accessors look like public instance variables, so he wisely allowed for the syntax sugar to do so. If a is an Array, then: a[i] = j means: a.add_at_index(i, j) And similarly for Hashes and Strings. However, the semantic for [] on Proc and Method objects is: a[i] <==> a.call(i) However, I cannot think of any meaningful name for: a[i] = j Which in your example turns into: a.call(i, j) Even stranger might be: a[i, j, k] = l, m, n #=> becomes a.call(i, j, k, [l, m, n]) In other words, for Proc and Method, [] is taken to mean "execute encapsulated code." However, "execute encapsulated code equals" doesn't make sense to me, at least in the context of Ruby. > This is not symetric. Ruby could always treat assignment as an operation > on an object. This requires: a Lvalue class and the ability to redefine > the assignment operator. As a result a variable could easily be an instance > of Lvalue and we would get closer to the motto "Everything is an object in > Ruby". I am currently prototyping such a Lvalue class. I don't really see how this would work. It would require a fundamental change in the way variables work in Ruby. For example, what happens when you do: c = Foo.new If c is an Lvalue class, then the variable that c refers to becomes a new Foo object. Otherwise, c becomes a new instance of Foo. However, this means that all Ruby variables would have to be by-value instead of by-reference as they are now. For example, if you did: c = b Currently c and b refer to the same object. In order to make your changes work, this would not be possible. I think this is a step backwards. It makes programmers think about what kind of memory they want to use (heap or stack, by-value or by-reference?). Not to mention that by-reference would be by far more used, considering Ruby has essentially _no_ by value variables at present and it does just fine. This is all not to mention the fact that Ruby variables are untyped, so they need to be able to refer to amounts of memory to handle arbitrarily large objects. The above scheme would require something similar to #become in Smalltalk. The difference would be that for most objects, assignment would default to #become, rather than it being an uncommonly used method. > If there was a Lvalue class, and if p was to return a lvalue, then I guess > p.call = a, b, c > would be equivalent to > lvalue = a, b, c > which is equivalent to > lvalue = a. > I would then expect the lvalue to be assigned a. Either by calling > lvalue.=(a) or directly by the interpretor. > > But there is no Lvalue class today. The closer is a user defined Pointer > class: p = lambda { ... return an instance of Pointer } > p.call()[]= a, b, c > eqv p.call().[]=(a) > > As you can see, with a Pointer, you have to dereference yourself using [], > whereas with a Lvalue you would not. Based on what I said above, I think it's by far better to require someone to use a pointer class to make non-local variable assignments. The actual need for them doesn't come up particularly often, and the consequences of making assignment object-based are, if you ask me, bad. > I made 2 mistakes, self is not the Proc object, x gets scoped out. > r.replace { x } would work, but then r = proc { x } is more obvious. > I don't know how to get the current Proc object from the proc's body. > Any clue ? Well, in general, you'll have r = lambda { ... } The block captures the scope in which it resides, so you can use r in the block to refer to the Proc itself. That's the only way, as far as I know. > Well, in this case you call long_method even if the proc is never called. > That was not the purpose of the example. The example is a cache where > long_method is call at most once, but only if needed (cached & lazy !). True. My last example isn't lazy. > I suspect it makes sense that a value is cached precisely when it is > often needed. With replace() the overhead of replace() decreases as > you need the cached value more and more, at some point it becomes > negligeable. Note that this does in fact work: def foo puts "foo" 6 end r = lambda { x = foo r = lambda { x } x } p r.call p r.call p r.call This only calls #foo once, and properly caches the result. However, this is only because we're referring to r by that variable each time. Passing it to def bar(p) p.call p.call end Results in #foo being called twice. However, I'd still say that Proc objects shouldn't be mutable (in the traditional sense). That's merely my opinion, though. Cheers. - Dan