Subclassing Struct.new


#1

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
=> #
irb(main):007:0> f.dec_n
NoMethodError: undefined method -' for nil:NilClass from (irb):3:indec_n’
from (irb):7
from :0
irb(main):008:0>

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

  • Minkoo S.

#2

On 2/28/06, Minkoo S. removed_email_address@domain.invalid wrote:

Hi group.

Please have a look at the following:

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:
http://feeds.feedburner.com/blogspot/liquiddevelopment


#3

It works! Thank you.

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

  • Minkoo S.

#4

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 S.


#5

On 2/28/06, Minkoo S. removed_email_address@domain.invalid wrote:

It works! Thank you.

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

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.

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


#6

Ross B. 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 S.


#7

On Tue, 2006-02-28 at 21:03 +0900, Minkoo S. 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.

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.


#8

On 2/28/06, Ross B. removed_email_address@domain.invalid 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?

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


#9

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

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?

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).

#10

On 2/28/06, Ross B. removed_email_address@domain.invalid wrote:

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).

I would disagree on the third point.

However, I guess that doing allowing ‘selfless’ attributes would cause
some worst inconsistency somewhere else. i just wonder what it could
be.

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


#11

Hello,

Minkoo S. 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


#12

Quoting chiaro scuro removed_email_address@domain.invalid:

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


#13

On 2/28/06, Markus W. removed_email_address@domain.invalid wrote:

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

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


#14

Quoting Ross B. removed_email_address@domain.invalid:

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


#15

Quoting Minkoo S. removed_email_address@domain.invalid:

n=1 is fine, but

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

-mental


#16

On 2/28/06, removed_email_address@domain.invalid removed_email_address@domain.invalid wrote:

Quoting chiaro scuro removed_email_address@domain.invalid:

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?

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?


#17

Quoting chiaro scuro removed_email_address@domain.invalid:

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


#18

On 2/28/06, removed_email_address@domain.invalid removed_email_address@domain.invalid wrote:

On Feb 28, 2006, at 9:55 AM, chiaro scuro wrote:
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:
http://feeds.feedburner.com/blogspot/liquiddevelopment


#19

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.

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 W.


#20

Quoting chiaro scuro removed_email_address@domain.invalid:

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’s not a matter of being defensive… because variables and method
names are gramatically different, there are some constructs where
one is (gramatically) valid but not the other. The decision has to
be made up-front, or the grammar drastically revised.

-mental