Навіщо сетерам Рубі потрібне "Я". кваліфікація всередині класу?


76

Рубі-сетери - створені (c)attr_accessorчи вручну, - схоже, є єдиними методами, які потребують self.кваліфікації при доступі до самого класу. Це, здається, ставить Рубі у світ мов:

  • Всі методи потребують self/ this(як Perl, і я думаю, що Javascript)
  • Жодні методи не вимагають self/ thisє (C #, Java)
  • Тільки сетерам потрібно self/ this(Ruby?)

Найкраще порівняння - C # та Ruby, оскільки обидві мови підтримують методи доступу, які працюють синтаксично так само, як змінні екземпляра класу: foo.x = y, y = 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 =()і це не вдається (Ruby 1.8.6 на Linux і OS X). Тепер 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();
  }
}

Питання: це правда? Чи є інші випадки, крім сетерів, де самообслуговування потрібно? Тобто, чи бувають інші випадки, коли метод 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. неоднозначний: лексична двозначність (лекс повинен «дивитися вперед»)
  2. Неоднозначне: граматична двозначність (yacc повинен відкласти аналіз дерева аналізу)
  3. АМБІГУЙ: двозначність, знаючи все на момент страти

(а також мотлоху між 2-3 теж). Усі ці категорії вирішуються шляхом збору більше контекстної інформації, все більш глобального пошуку. Отже, коли ви говорите:

"qwerty = 4" є НЕМАГБІЗНИМ у C #, коли не визначено змінну ...

Я не міг більше погодитися. Але тим самим я кажу

"qwerty = 4" є недвозначним у рубіні (як зараз існує)

"qwerty = 4" є неоднозначним у C #

І ми ще не суперечимо один одному. Нарешті, тут ми справді не погоджуємось: будь-який ruby ​​міг або не міг бути реалізований без подальших мовних конструкцій, таких

Для "qwerty = 4" ruby ​​UNAMBIGUOUSLY викликає існуючий сеттер, якщо
не визначено локальну змінну

Ви кажете ні. Я кажу так; міг би існувати інший рубін, який поводиться точно так само, як струм, у всіх відношеннях, за винятком того, що "qwerty = 4" визначає нову змінну, коли не існує сеттера та місцевого, він викликає сеттера, якщо такий існує, і присвоює локальному, якщо такий існує. Я цілком визнаю, що можу помилитися. Насправді, причина, з якої я можу помилятися, була б цікавою.

Дозволь пояснити.

Уявіть, що ви пишете нову мову OO за допомогою методів доступу, схожих на екземпляри vars (наприклад, ruby ​​& C #). Ви, мабуть, почали б з концептуальних граматик приблизно такого:

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

Але компілятор синтаксичного аналізатора (навіть не час виконання) буде блюти, тому що навіть після того, як усі введені дані введені, неможливо дізнатись, яка граматика має значення. Ви стикаєтесь з тим, який класичний вибір. Я не можу бути впевнений у деталях, але в основному рубін робить це:

  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 ​​міг зробити те саме, але "пізніше" повинен був бути під час виконання, тому що, як зазначає Бен, ви не знаєте, доки не буде виконано твердження, який випадок застосовується.

Моє запитання ніколи не означало "чи мені справді потрібне" я "?" або "якої потенційної двозначності уникають?" Швидше я хотів знати, чому був зроблений саме цей вибір? Можливо, це не продуктивність. Можливо, це просто зробило роботу, або вважалося найкращим завжди дозволяти локальному пристрою з 1 лайнером перевизначати метод (досить рідкісна вимога) ...

Але я начебто припускаю, що найбільш динамічною мовою може бути та, яка найдовше відкладає це рішення, і вибирає семантику на основі найбільш контекстуальної інформації: отже, якщо у вас немає локальної мови і ви визначили сеттера, він буде використовувати сетер . Чи не тому ми любимо ruby, smalltalk, objc, оскільки виклик методу вирішується під час виконання, пропонуючи максимальну виразність?


PHP також вимагає $this->доступу до змінних екземплярів. Це мене постійно спонукає.
Хлоя

Явний приймач потрібен лише для методів класу, а не методів екземпляра.
Тодд А. Джейкобс,

Я згоден - мені також не подобається такий спосіб вирішення спорідненості. Порушує принцип найменшого здивування.
Dogweather

@Dogweather Matz уточнює, що він має на увазі під найменшим здивуванням : кожна людина має своє індивідуальне походження. ... вони можуть бути здивовані різними аспектами мови. Потім вони підходять до мене і кажуть: "Я був здивований цією особливістю мови, тому Рубі порушує принцип найменшого здивування". Чекай. Чекай. Принцип найменшого сюрпризу - це не тільки для вас. Принцип найменшого подиву означає принцип найменшого мого подиву. І це означає принцип найменшого здивування після того, як ти дуже добре вивчиш Рубі.
Келвін

Відповіді:


19

Тут важливо пам’ятати, що методи Ruby можна (не) визначати в будь-який момент, тому для розумного вирішення двозначності кожному присвоєнню потрібно буде запустити код, щоб перевірити, чи існує метод із іменем, якому присвоєно на той момент. призначення.


18
Це не правильно, справжня причина полягає в тому, що просто вказуючи x=5екземпляри локальної змінної, xяка перекриває будь-який існуючий сеттер self.x=. Щоб вирішити цю двозначність, якщо ви хочете зателефонувати сетеру, x=вам потрібно чітко сказати, що ви хочете зробити, зазначившиself.x=
bbozo

85

Ну, я думаю, що причина в цьому полягає в тому qwerty = 4, що неоднозначна - ви визначаєте нову змінну, що викликається, qwertyабо викликаєте сеттера? Рубі вирішує цю неоднозначність, кажучи, що вона створить нову змінну, тому 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 # насправді не є хорошим порівнянням, оскільки ви визначаєте змінні таким чином, щоб це було однозначно з використанням сеттера. Якби ви визначили змінну в 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

1
До цього слід додати, що Java та подібні до неї причини усуваються, тому що, будучи статично набраними мовами, компілятор може заздалегідь визначити, чи буде конфлікт, і відповідно викликати відповідну помилку. Динамічні (мови сценаріїв), слабко набрані (php) та качині (ruby / python) мови не мають такої розкоші, на жаль
Шейн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.