为什么Ruby setter需要“自我”。班内资历?


76

Ruby设置方法(无论是由(c)attr_accessor手工创建还是由手工创建),似乎是self.在类内部进行访问时唯一需要限定的方法。这似乎使Ruby成为语言的世界:

  • 所有方法都需要self/ this(例如Perl,我认为是Javascript)
  • 没有方法需要self/this是(C#,Java)
  • 只有设置员需要self/ this(Ruby吗?)

最好的比较是C#与Ruby,因为这两种语言都支持访问器方法,这些访问器方法在语法上类似于类实例变量:foo.x = yy = foo.x。C#称它们为属性。

这是一个简单的例子;在Ruby中使用相同的程序,然后使用C#:

class A
  def qwerty; @q; end                   # manual getter
  def qwerty=(value); @q = value; end   # manual setter, but attr_accessor is same 
  def asdf; self.qwerty = 4; end        # "self." is necessary in ruby?
  def xxx; asdf; end                    # we can invoke nonsetters w/o "self."
  def dump; puts "qwerty = #{qwerty}"; end
end

a = A.new
a.xxx
a.dump

带走它self.qwerty =(),它就会失败(Linux&OS X上为Ruby 1.8.6)。现在使用C#:

using System;

public class A {
  public A() {}
  int q;
  public int qwerty {
    get { return q; }
    set { q = value; }
  }
  public void asdf() { qwerty = 4; } // C# setters work w/o "this."
  public void xxx()  { asdf(); }     // are just like other methods
  public void dump() { Console.WriteLine("qwerty = {0}", qwerty); }
}

public class Test {
  public static void Main() {
    A a = new A();
    a.xxx();
    a.dump();
  }
}

问题:这是真的吗?除了传教士以外,还有其他需要自我的场合吗?即,在其他情况下,没有self不能调用Ruby方法吗?

当然,在很多情况下自我必要的。请注意,这不是Ruby独有的:

using System;

public class A {
  public A() {}
  public int test { get { return 4; }}
  public int useVariable() {
    int test = 5;
    return test;
  }
  public int useMethod() {
    int test = 5;
    return this.test;
  }
}

public class Test {
  public static void Main() {
    A a = new A();
    Console.WriteLine("{0}", a.useVariable()); // prints 5
    Console.WriteLine("{0}", a.useMethod());   // prints 4
  }
}

以相同的方式解决相同的歧义。但是,尽管我很微妙,但我想问的是

  • 一种方法已经被确定,并
  • 没有局部变量已经被定义,

我们遇到

qwerty = 4

哪个是模棱两可的-这是方法调用还是新的局部变量分配?


@迈克·斯通

嗨!我理解并感谢您提出的观点,并且您的榜样很棒。当我说的时候,请相信我,如果我有足够的声誉,我会投票赞成你的回答。但是我们仍然不同意:

  • 关于语义,以及
  • 以事实为中心

首先,我不无讽刺地宣称,我们正在就“歧义”的含义进行语义辩论。

在解析和编程语言语义(这个问题的主题)时,您肯定会接受“歧义”这一概念。让我们采用一些随机符号:

  1. 模棱两可:词汇歧义(lex必须“向前看”)
  2. 歧义:语法歧义(yacc必须遵从解析树分析)
  3. 歧义:执行时一无所知

(还有2-3之间的垃圾)。通过收集更多上下文信息,在全球范围内查找越来越多,可以解决所有这些类别。所以当你说

当未定义变量时,C#中的“ qwerty = 4”是不确定的...

我完全同意。但出于同样的原因,我是说

“ qwerty = 4”在红宝石中是明确的(因为它现在存在)

“ qwerty = 4”在C#中不明确

而且我们还没有相互矛盾。最后,这是我们真正不同意的地方:如果没有任何其他语言结构(例如,

对于“ qwerty = 4”,如果
未定义局部变量,ruby会不确定地调用现有的setter

你说不 我说是; 可能存在另一个在各个方面都与当前行为完全相同的红宝石,只是“ qwerty = 4”在没有设置器且不存在本地变量的情况下定义了一个新变量,如果存在则调用设置器,如果存在则将其分配给本地。我完全接受我可能是错的。实际上,我可能错了的原因很有趣。

让我解释。

想象一下,您正在使用一种类似于实例vars(如ruby和C#)的访问器方法来编写一种新的OO语言。您可能会从以下概念语法开始:

  var = expr    // assignment
  method = expr // setter method invocation

但是解析器编译器(甚至不是运行时)都会呕吐,因为即使在所有输入都被搞混之后,也无法知道哪种语法是相关的。您将面对哪个经典选择。我不确定细节,但是基本上ruby做到了:

  var = expr    // assignment (new or existing)
  // method = expr, disallow setter method invocation without .

这就是为什么它没有歧义的原因,而C#做到了:

  symbol = expr // push 'symbol=' onto parse tree and decide later
                // if local variable is def'd somewhere in scope: assignment
                // else if a setter is def'd in scope: invocation

对于C#,“稍后”仍处于编译时。

我确定ruby可以做到这一点,但是“以后”必须在运行时进行,因为正如ben指出的那样,在执行该语句之前您不知道哪种情况适用。

我的问题从未打算表示“我真的需要'自我'吗?” 或“正在避免什么潜在的歧义?” 而是我想知道为什么要做出这个特定选择?也许不是性能。也许它只是完成了工作,或者最好总是允许一个1-liner局部方法覆盖方法(这是非常罕见的情况要求)...

但我有点建议,最具动态性的语言可能是最长延迟此决定的语言,并根据最上下文的信息选择语义:因此,如果您没有本地语言并且定义了设置器,则将使用该设置器。这不是为什么我们喜欢ruby,smalltalk和objc,因为方法调用是在运行时决定的,从而提供了最大的表达能力吗?


PHP$this->在访问实例变量时也需要。这让我无时无刻不在。
Chloe 2014年

一个显式的接收者只需要类方法,而不是实例方法。
Todd A. Jacobs

我同意–我也不喜欢这种解决贪婪的方法。违反了最不惊奇的原则。
Dogweather

@Dogweather马茨澄清他所说的东西至少惊讶:每个人都有一个单独的背景。...他们可能会对语言的不同方面感到惊讶。然后他们走近我说:“我对该语言的这一功能感到惊讶,因此Ruby违反了最小惊讶原则。” 等待。等待。最少惊喜的原则不只适合您。最少惊喜原则意味着最少惊喜。这意味着在您很好地学习了Ruby之后,最少惊讶的原则。
开尔文

Answers:


19

这里要记住的重要一点是,可以在任何时候定义(未定义)Ruby方法,因此要智能地解决歧义,每个分配都需要运行代码来检查当时是否存在一个具有分配给名称的方法。的任务。


18
这是不正确的,真正的原因是仅仅声明x=5实例化了一个x覆盖任何现有setter的局部变量self.x=。为了解决这种歧义,如果您想打电话x=self.x=
塞特犬

85

好吧,我认为是这种情况的原因是因为qwerty = 4模棱两可-您要定义一个新变量qwerty还是调用setter?Ruby通过说它将创建一个新变量来解决这种歧义,因此self.是必需的。

这是您需要的另一种情况self.

class A
  def test
    4
  end
  def use_variable
    test = 5
    test
  end
  def use_method
    test = 5
    self.test
  end
end
a = A.new
a.use_variable # returns 5
a.use_method   # returns 4

如您所见,访问test是不明确的,因此self.是必需的。

同样,这就是为什么C#示例实际上不是一个很好的比较的原因,因为您以与使用setter明确的方式定义变量。如果您在C#中定义了一个与访问器同名的变量,则需要使用来限定对访问器的调用this.,就像Ruby的情况一样。


17

因为否则将不可能在方法内部全部设置局部变量。variable = some_value是模棱两可的。例如:

class ExampleClass
  attr_reader :last_set
  def method_missing(name, *args)
    if name.to_s =~ /=$/
      @last_set = args.first
    else
      super
    end
  end

  def some_method
    some_variable = 5 # Set a local variable? Or call method_missing?
    puts some_variable
  end
end

如果self不需要二传手,some_method那就提出来NameError: undefined local variable or method 'some_variable'。不过,该方法按预期工作:

example = ExampleClass.new
example.blah = 'Some text'
example.last_set #=> "Some text"
example.some_method # prints "5"
example.last_set #=> "Some text"

8
哈哈。我本来打算提这个答案,然后才意识到这是一年前的事了。;-)
Ajedi32

我认为这是唯一可以清楚阐明为什么不能没有的答案self.
amoebe 2015年

1
我还要补充一点,Java及其同类之所以能摆脱它,是因为作为静态类型的语言,编译器可以预先确定是否会发生冲突,并相应地引发适当的错误。动态(脚本语言),弱类型(php)和鸭子类型(红宝石/ python)语言没有这么奢侈,a
Shayne
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.