“Anti-private” property of setter method

2019-06-14 10:14发布

Getter methods can be used without an explicit receiver unless there is a local variable with the same name:

class A; attr_reader :foo end
A.new.instance_eval do
  @foo = :foo
  p foo
end
# => :foo

This will not hold when there is a local variable with the same name, due to the principle that interpretation as a local variable has priority than as a method call whenever there is an ambiguity.

class A; attr_reader :foo end
A.new.instance_eval do
  foo = :bar
  @foo = :foo
  p foo
end
# => :bar

However, setter methods cannot be used without an explicit receiver even when a local variable with the same name is not assigned prior to the expression in question:

class A; attr_writer :foo end
A.new.instance_eval do
  foo = :foo  # <= No local variable named `foo` has been assigned before this point
  p @foo
end
# => nil

How is this "anti-private" property of setter method justified?

3条回答
成全新的幸福
2楼-- · 2019-06-14 10:32

If ruby interpreted your assignment in your last statement as an assignment to self, you would have no way left to set a local variable.

The way it is leaves no ambiguity for the interpreter to deal with: assignments without self are always local variables, assignments to self are always trying to use a writer on the object.


If it were the other way around

The interpreter would have to look up the contexts writer methods and assign it via the writer if there is one, which almost certainly would have a negative impact on performance

class A
  attr_writer :foo
end

A.new.instance_eval do
  # for each of these assignments, the interpreter has to look up if there's
  # a writer method defined
  foo = 'bar' 
  bar = 'baz'
  fib = 'buz'
end

It would also leave the programmer with the rather stupid task to find out every setter method of the context he's in before assigning local variables to make absolutely sure he does not unintentionally use a setter.

class C
  attr_writer :something
end

class B < C
  attr_writer :foo
end

class A < B
  attr_writer :bar
end

A.new.instance_eval
  something = 'something' 
  #you just (almost certainly with no intention) assigned a value to an attribute
end

Also, your question reads:

setter methods cannot be used without an explicit receiver even when a local variable with the same name is not assigned prior to the expression in question:

If it were the other way around, you could not assign a local variable with the same name prior to the expression in question, because the assignment would use the setter (as stated in the first paragraph of this answer)

Concerning the implementation / the access to variables the attribute methods use: Getter and Setters work with instance variables. So, for example attr_accessor actually defines something like this:

 def foo
   @foo
 end

 def foo=(data)
   @foo = data
 end

So, the attribute is declared as a instance variable and not as a local variable, why should the programmer be able to assign it like a local variable? This would leave the wrong impression that you could assign instance variables of an object via assigning local variables. If ruby would do this, it would almost certainly lead to a serious memory management problem. To make it short: foo = 'bar' and @foo = 'bar' are not the same, and exactly because the attr methods use @foo = 'bar', you can not call them via using foo = 'bar'.

查看更多
混吃等死
3楼-- · 2019-06-14 10:32

I think @sawa finally clarified what is meant by "anti-private".

sawa's comment:

Private means it cannot have an explicit receiver. Negation of that would be that it may have an explicit receiver, which is not what I am mentioning. I am mentioning a case where a method must have an explicit receiver, which is against private. I think you are confused.

I was confused, apparently along with all the other commenters, because "anti-private" and "against private" aren't standard terminology, nor was the meaning immediately obvious.

I think the meaning of the original question is: "Since setters require an explicit receiver, and private forbids explicit receivers, how can I call a private setter?" In other words, "anti-private" means "incompatible with private", or "unusable with private".

Jörg W Mittag eloquently explains an exception to the normal private rules. Basically, setters can be called on self even if they are private, because there's no other way to call them (unless you use the cumbersome send).

So, a setter's requirement of an explicit receiver is perfectly compatible with the setter being private, only because of the exception to the rule.

查看更多
仙女界的扛把子
4楼-- · 2019-06-14 10:39

Beat Richartz's answer is pretty complete already, but I want to highlight one point about the behavior you're proposing.

In your question you have this sample code:

class A; attr_writer :foo end
A.new.instance_eval do
  foo = :foo  # <= No local variable named `foo` has been assigned before this point
  p @foo
end

You are proposing that the assignment call the setter method. And you want this to happen if the local-variable foo hasn't been assigned yet.

But what syntax would you use to assign the local before that point?

If the receiverless assignment foo = :foo means call the setter (when it exists), you'd need yet another syntax construct to mean "assign this local-variable, disregarding whether there is a setter".

I honestly do want to hear your proposal (I'm not being sarcastic) if you have one. It would be interesting to hear alternative views on language design.

I'm not saying your way would be necessarily "worse" than the current ruby way. But at some point a language designer has to decide default behaviors for ambiguous situations, and Matz decided that receiverless assignment assigns the local.

查看更多
登录 后发表回答