Subclassing Struct.new

Hi group.

Please have a look at the following:

irb(main):001:0> class F < Struct.new :n
irb(main):002:1> def dec_n
irb(main):003:2> n = n - 1
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> f = F.new 1
=> #<struct F n=1>
irb(main):007:0> f.dec_n
NoMethodError: undefined method `-' for nil:NilClass
        from (irb):3:in `dec_n'
        from (irb):7
irb(main):008:0>

I've tried to define a class which decrease n by 1, but failed. How can
I do that?

- Minkoo Seo

···

from :0

The following works for me. I suspect you should have used self
inside the method

class F < Struct.new(:n)
    def dec_n
        self.n -= 1
    end
end

f = F.new 2
puts f.n == 2

f.dec_n
puts f.n == 1

-- Chiaroscuro --
Liquid Development Blog: Liquid Development

···

On 2/28/06, Minkoo Seo <minkoo.seo@gmail.com> wrote:

Hi group.

Please have a look at the following:

It works! Thank you.

But, could you tell me why should I have to use self?

- Minkoo Seo

The alternative would be to use @n, but I am not sure that Struct
organises these properties around individual instance variable. it
probably keeps them in an internal hash, so you have to use the
accessor 'n'. However, when you use it on the left handside you must
prefix with self, otherwise ruby thinks it is a variable n, rather
than a call to the attribute writer 'n='.

I am not sure if this is supposed to be a bug.. it's certainly a
surprising behaviour.

···

On 2/28/06, Minkoo Seo <minkoo.seo@gmail.com> wrote:

It works! Thank you.

But, could you tell me why should I have to use self?

--

-- Chiaroscuro --
Liquid Development Blog: Liquid Development

chiaro scuro wrote:

However, when you use it on the left handside you must
prefix with self, otherwise ruby thinks it is a variable n, rather
than a call to the attribute writer 'n='.

It is strange because

n=1 is fine, but
n -= 1 is not.

I am not sure if this is supposed to be a bug.. it's certainly a
surprising behaviour.

Counter intuitive, at the very least. I really want this behavior to be
fixed.

Best,
Minkoo Seo

Quoting chiaro scuro <kiaroskuro@gmail.com>:

I am not sure if this is supposed to be a bug.. it's certainly a
surprising behaviour.

Since variables are declared via first assignment, how could it be
done differently?

-mental

Well, n = 1 just assigns the fixnum 1 to a (new) local variable 'n'.
n -= 1 is expanded to n = n + 1.

Normally, n + 1 would end up calling your method because Ruby would have
to figure out whether it's a method or variable, but because in this
case Ruby has seen a bare assignment to 'n' by that point, it remembers
that and assumes 'n' is a local variable.

This local variable isn't yet initialized (the n + 1 would be it's
initializer), so n + 1 ends up being nil + 1, or (effectively) nil.+(1),
hence the "undefined method '+' for nil:NilClass".

Using self.n = 1 forces Ruby to treat the assignment as involving the
method 'n' on 'self'.

I don't think it's a bug, but I know it's tripped people (including me)
up before. From the implementation point of view it's probably the
lesser of two evils, though.

···

On Tue, 2006-02-28 at 21:03 +0900, Minkoo Seo wrote:

chiaro scuro wrote:
> However, when you use it on the left handside you must
> prefix with self, otherwise ruby thinks it is a variable n, rather
> than a call to the attribute writer 'n='.

It is strange because

n=1 is fine, but
n -= 1 is not.

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

Hello,

Minkoo Seo wrote:

Counter intuitive, at the very least. I really want this behavior to be
fixed.

How should it be in your opinion?

I find it intuitive, cause the sense behind it is obviously.

Ruby assumes that a method call has the current object as receiver.
So it implied the "self." prefix"
But if there is a local var assignment it assumes that you like to use this
local var instead.
Since the local var is determined by the enclosing block it is IMHO an
intuitive solution.

What do you ruby expect to do if it see some think like this?

def foo
  a = "hello"
end

Should it assign the local var "a" or should it call the method "a="?
If you say that it should assign the local var "a" just if there is no
method "a=".

What happend to your code if some day in the future you or some else define
a methode "a=" to write the ivar @a. And you don't remember that your
method foo use a local var a?

with kind regards

Markus

PS
What should happend in this case?

def n
  @n
end

def n=(n)
    @n = n
end

Quoting Minkoo Seo <minkoo.seo@gmail.com>:

n=1 is fine, but

Check again. n=1 will parse as assignment to the local variable 'n'
too.

-mental

I guess that in principle when you see an '=', you could lookup a
'name=' method before deciding that it is a local var assignment
rather than a method call.

I also understand that there will be perfromance issues with that.

did I miss something?

···

On 2/28/06, mental@rydia.net <mental@rydia.net> wrote:

Quoting chiaro scuro <kiaroskuro@gmail.com>:

> I am not sure if this is supposed to be a bug.. it's certainly a
> surprising behaviour.

Since variables are declared via first assignment, how could it be
done differently?

Minkoo Seo wrote:

chiaro scuro wrote:

However, when you use it on the left handside you must
prefix with self, otherwise ruby thinks it is a variable n, rather
than a call to the attribute writer 'n='.

It is strange because

n=1 is fine, but
n -= 1 is not.

I am not sure if this is supposed to be a bug.. it's certainly a
surprising behaviour.

Counter intuitive, at the very least. I really want this behavior to be
fixed.

Please explain what you mean.

Hal

Ross Bamford wrote:

Normally, n + 1 would end up calling your method because Ruby would have
to figure out whether it's a method or variable, but because in this
case Ruby has seen a bare assignment to 'n' by that point, it remembers
that and assumes 'n' is a local variable.

This local variable isn't yet initialized (the n + 1 would be it's
initializer), so n + 1 ends up being nil + 1, or (effectively) nil.+(1),
hence the "undefined method '+' for nil:NilClass".

Sigh. This reminds me of the rule that I have to use
"this->method_name( )" when I want call a method of T, parent class,
which is passed as template parameter. Anyway, it's not a perfect
world...

Best,
Minkoo Seo

It's really unrubesque and tricky. What would be the consequences of
handling it as you would normally expect? What would the side effects
be?

···

On 2/28/06, Ross Bamford <rossrt@roscopeco.co.uk> wrote:

This local variable isn't yet initialized (the n + 1 would be it's
initializer), so n + 1 ends up being nil + 1, or (effectively) nil.+(1),
hence the "undefined method '+' for nil:NilClass".

Using self.n = 1 forces Ruby to treat the assignment as involving the
method 'n' on 'self'.

I don't think it's a bug, but I know it's tripped people (including me)
up before. From the implementation point of view it's probably the
lesser of two evils, though.

--

-- Chiaroscuro --
Liquid Development Blog: Liquid Development

Should it assign the local var "a" or should it call the method "a="?
If you say that it should assign the local var "a" just if there is no
method "a=".

thanks for pointing out the side effects of the other stylistic
choice. now it's much clearer why it was done this way.

What happend to your code if some day in the future you or some else define
a methode "a=" to write the ivar @a. And you don't remember that your
method foo use a local var a?

my tests would fail, I guess.

I also program in VBA, where you have a 'property set' feature that is
roughly the equivalent of the 'name=' operator in Ruby and it has
never been a problem. the few times this error slipped through, the
tests always caught it.

What should happend in this case?

def n
  @n
end

def n=(n)
    @n = n
end

I guess the 'n' in '@n = n' would be the one most internally scoped,
i.e. the passed arg

···

On 2/28/06, Markus Werner <markus.werner+news@wobcom.de> wrote:

--

-- Chiaroscuro --
Liquid Development Blog: http://feeds.feedburner.com/blogspot/liquiddevelopment

Quoting Ross Bamford <rossrt@roscopeco.co.uk>:

From the implementation point of view it's probably the
lesser of two evils, though.

It's really the only possible way; the variable-versus-writer
decision has to be made at parse time.

-mental

The problem, of course, is that methods can come and go so you would
have to check every time the assignment was executed and I don't
think you would want the assignment to be to a local variable at one
point and then to be a method call at another.

It has to be determined statically. So any 'solution' to the problem
would probably be to introduce some clear syntactical hint with
respect to local variables...

Gary Wright

···

On Feb 28, 2006, at 9:55 AM, chiaro scuro wrote:

I guess that in principle when you see an '=', you could lookup a
'name=' method before deciding that it is a local var assignment
rather than a method call.

I also understand that there will be perfromance issues with that.

Quoting chiaro scuro <kiaroskuro@gmail.com>:

I guess that in principle when you see an '=', you could lookup a
'name=' method before deciding that it is a local var assignment
rather than a method call.

I also understand that there will be perfromance issues with
that.

did I miss something?

In Ruby's grammar, expressions containing a name parse differently
depending on whether that name is a variable or a method.

Whatever rule is used has to be able to distinguish between the two
at parse-time; you can't rely on run-time information.

So, the current parsing rule is: "If you see an assignment to a bare
name, that name means a variable until the end of the block/method.
Until then it means a method call."

e.g.:

def foo
   n # method
end

def bar
   n = 3
   n # variable
end

def baz
   n # method
   n = 3
   n # variable
end

Note that it doesn't matter if the assignment is actually performed,
just that the parser sees it:

def hoge
   n # method
   if false
     n = 3
   end
   n # variable
end

(uninitialized variables are nil)

-mental

This isn't necessarily based in fact (i.e. I've not surfed the source
just now) but I imagine it would be:

  * Harder to parse, with more ambiguous cases.
  * Generally slower, since local lookup would be 'the long way'
    more often.
  * More difficult to follow the code six months down the line
    (or on the next guy's screen).

···

On Tue, 2006-02-28 at 22:14 +0900, chiaro scuro wrote:

On 2/28/06, Ross Bamford <rossrt@roscopeco.co.uk> wrote:
> This local variable isn't yet initialized (the n + 1 would be it's
> initializer), so n + 1 ends up being nil + 1, or (effectively) nil.+(1),
> hence the "undefined method '+' for nil:NilClass".
>
> Using self.n = 1 forces Ruby to treat the assignment as involving the
> method 'n' on 'self'.
>
> I don't think it's a bug, but I know it's tripped people (including me)
> up before. From the implementation point of view it's probably the
> lesser of two evils, though.

It's really unrubesque and tricky. What would be the consequences of
handling it as you would normally expect? What would the side effects
be?

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

The problem, of course, is that methods can come and go so you would
have to check every time the assignment was executed and I don't
think you would want the assignment to be to a local variable at one
point and then to be a method call at another.

that wouldn't worry me. it's a rare event that tests would probably
catch anyway.
I understand that it has been implemented statically, and that the
whole mechanism would need changing, but don't you agree that this
syntactic tagging and static interpretation of '=' is a kind of
defensive programming that we could do well without?

It has to be determined statically. So any 'solution' to the problem
would probably be to introduce some clear syntactical hint with
respect to local variables...

-- Chiaroscuro --
Liquid Development Blog: Liquid Development

···

On 2/28/06, gwtmp01@mac.com <gwtmp01@mac.com> wrote:

On Feb 28, 2006, at 9:55 AM, chiaro scuro wrote:

Hi,

So, the current parsing rule is: "If you see an assignment to a bare
name, that name means a variable until the end of the block/method.
Until then it means a method call."

Yes, and in Ruby2.0, this will be changed to "if you see an assignment
to a bare name, that name means a variable in the current scope", so
that

def foo
  n # method
end

def bar
  n = 3
  n # variable
end

def baz
  n # will be variable
  n = 3
  n # variable
end

Note that it doesn't matter if the assignment is actually performed,
just that the parser sees it:

def hoge
  n # will be variable
  if false
    n = 3
  end
  n # variable
end

See?

              matz.

···

In message "Re: Subclassing Struct.new" on Wed, 1 Mar 2006 00:13:13 +0900, mental@rydia.net writes: