Змішування приватних та публічних атрибутів та аксесуарів у Раку


12
#Private attribute example
class C { 
    has $!w;                            #private attribute
    multi method w { $!w }              #getter method
    multi method w ( $_ ) {                 #setter method
        warn “Don’t go changing my w!”;   #some side action
        $!w = $_
    }  
}
my $c = C.new
$c.w( 42 )
say $c.w #prints 42
$c.w: 43
say $c.w #prints 43

#but not
$c.w = 44
Cannot modify an immutable Int (43)

поки що, так розумно, і то

#Public attribute example
class C { 
    has $.v is rw    #public attribute with automatic accessors
}
my $c = C.new
$c.v = 42
say $c.v #prints 42

#but not
$c.v( 43 ) #or $c.v: 43
Too many positionals passed; expected 1 argument but got 2

Мені подобається безпосередність присвоєння '=', але мені потрібна легкість перегляду побічних дій, які надають різні методи. Я розумію, що це два різних світи, і що вони не змішуються.

АЛЕ - я не розумію, чому я не можу просто перейти на $ cv (43), щоб встановити загальнодоступний атрибут

  1. Я відчуваю, що Раку наводить мене на те, щоб не змішувати ці два режими - деякі атрибути приватні та деякі публічні, і що тиск спрямований на метод методу (з деяким: цукор з товстої кишки) - чи є це наміром дизайну Раку?
  2. Я щось пропускаю?

Ви можете використовувати проксі
user0721090601

Як ви могли використовувати проксі? Я маю на увазі, що аксесуар вже повертає контейнер, коли is rwвказано. Повернення проксі не змінює кількість допустимих параметрів на аксесуарі.
Єлизавета Маттійсен

@ElizabethMattijsen Можливо, я неправильно зрозумів питання. Але це, здається, працює для того, що він хоче (з урахуванням = fooі .(foo)налаштування), і з можливістю робити побічні ефекти в обох випадках (але не лише тоді, коли вони отримані): tio.run/…
user0721090601

Відповіді:


13

це наміри дизайну Раку?

Справедливо сказати, що Раку не є цілком неприхильним у цій галузі. Ваше запитання стосується двох тем дизайну Раку, які обидва варто трохи обговорити.

Раку має першокласні l-значення

Раку рясно використовує значення l, будучи першокласною справою. Коли ми пишемо:

has $.x is rw;

Створений метод:

method x() is rw { $!x }

is rwТут вказує на те, що метод повертає л-значення - тобто, то , що може бути призначений. Таким чином, коли ми пишемо:

$obj.x = 42;

Це не синтаксичний цукор: це дійсно виклик методу, а потім до результату застосовується оператор призначення. Це спрацьовує, оскільки виклик методу повертає Scalarконтейнер атрибута, якому потім можна призначити. Можна використовувати прив'язку, щоб розділити це на два етапи, щоб побачити, що це не тривіальна синтаксична трансформація. Наприклад, це:

my $target := $obj.x;
$target = 42;

Призначатиметься атрибуту об'єкта. Цей самий механізм стоїть за численними іншими функціями, включаючи призначення списку. Наприклад, це:

($x, $y) = "foo", "bar";

Працює, побудувавши Listконтейнери , що містять $xі $y, а потім оператор призначення в цьому випадку повторює кожну сторону попарно, щоб виконати завдання. Це означає, що ми можемо використовувати rwтам об’єктні аксесуари:

($obj.x, $obj.y) = "foo", "bar";

І все це природно працює. Це також механізм присвоєння фрагментів масивів і хешів.

Можна також використовувати Proxyдля того, щоб створити контейнер із значенням l, де поведінка його читання та запису знаходиться під вашим контролем. Таким чином, ви можете вкласти побічні дії STORE. Однак ...

Раку заохочує семантичні методи над "сетерами"

Коли ми описуємо ОО, часто з'являються такі терміни, як "інкапсуляція" та "приховування даних". Ключова ідея тут полягає в тому, що модель стану всередині об'єкта - тобто спосіб, який він обирає представляти потрібні йому дані для реалізації своєї поведінки (методів) - вільний еволюціонувати, наприклад, для обробки нових вимог. Чим складніший об’єкт, тим більш визвольним стає він.

Однак геттери та сетери - це методи, що мають неявний зв’язок із державою. Хоча ми можемо стверджувати, що ми досягаємо приховування даних, тому що ми викликаємо метод, а не отримуємо доступ до стану безпосередньо, мій досвід полягає в тому, що ми швидко опиняємось у місці, де зовнішній код робить послідовності викликів сеттера для досягнення операції - що є форма функції заздрить анти-візерунку. І якщо ми робимо що , це досить впевнений , що ми в кінцевому підсумку з логікою зовні об'єкта , який робить суміш отримання і установки операцій для досягнення операції. Дійсно, ці операції повинні були бути викриті як методи з іменами, що описують, що досягається. Це стає ще важливішим, якщо ми знаходимося в одночасній обстановці; добре спроектований об'єкт часто досить легко захистити на межі методу.

Однак, багато використання classсправді є типом записів / продуктів: вони існують, щоб просто згрупувати купу елементів. Не випадково .sigil не просто генерує аксесуар, а й:

  • Визначає, що атрибут встановлюється за допомогою логіки ініціалізації об'єкта за замовчуванням (тобто, а class Point { has $.x; has $.y; }може бути ініційований як Point.new(x => 1, y => 2)), а також надає це в .rakuметоді демпінгу.
  • Включає атрибут в .Captureоб'єкт за замовчуванням , тобто ми можемо використовувати його в деструктуризації (наприклад, sub translated(Point (:$x, :$y)) { ... }).

Які речі ви б хотіли, якби ви писали у більш процедурному чи функціональному стилі та використовували classяк засіб визначення типу запису.

Дизайн Raku не оптимізований для виконання розумних речей у сеттерах, тому що це вважається поганою справою для оптимізації. Це понад те, що потрібно для типу запису; на деяких мовах ми можемо стверджувати, що ми хочемо зробити перевірку того, що призначено, але в Раку ми можемо звернутися до subsetтипів для цього. У той же час, якщо ми справді робимо проект OO, тоді ми хочемо API змістовної поведінки, що приховує модель стану, а не думати про терміни / сетери, які, як правило, призводять до невдалої колокації дані та поведінка, що все-таки має сенс робити ОО.


Хороший момент щодо обережності Proxys (хоча я запропонував це га). Єдиний раз, коли я знайшов їх страшенно корисними, це для моїх LanguageTag. Внутрішньо $tag.regionповертає об'єкт типу Region(як він зберігається всередині), але реальність така, що це набагато зручніше для людей , щоб сказати $tag.region = "JP"більше $tag.region.code = "JP". І це дійсно лише тимчасово, поки я не можу висловити примус із Strтипу, наприклад, has Region(Str) $.region is rw(який вимагає дві окремі заплановані, але низькі пріоритетні функції)
user0721090601

Дякую @Jonathan за те, що витратили час на розробку обгрунтування дизайну - я підозрював якийсь аспект з нафтою та водою, а також для таких, що не є CS, як я, я дійсно розумію різницю між належним OO із прихованим станом та застосуванням класу es як дружніший спосіб побудувати власників езотеричних даних, які б отримали ступінь доктора наук зі спеціальності C ++. І user072 ... для ваших думок про Proxy s. Я вже знав про Proxy s раніше, але підозрював, що вони (та / або риси) навмисно є досить обтяжливим синтаксисом, щоб
відмовити

p6steve: Раку справді створений для того, щоб зробити найпоширеніші / легкі речі дуже легко. Коли ви відхиляєтесь від звичайних моделей, це завжди можливо (я думаю, ви бачили приблизно три різні способи зробити те, що ви хочете досі ... і, безумовно, більше), але це змушує вас трохи попрацювати - достатньо, щоб зробити впевнений, що ти робиш, справді хочеш, щоб ти хотів. Але за допомогою ознак, проксі, сленгів тощо можна зробити так, що вам потрібно лише кілька зайвих символів, щоб дійсно включити якісь цікаві речі, коли вам це потрібно.
користувач0721090601

7

АЛЕ - я не розумію, чому я не можу просто перейти на $ cv (43), щоб встановити загальнодоступний атрибут

Ну, це справді залежить від архітектора. Але якщо серйозно, ні, це просто не стандартний спосіб роботи Раку.

Тепер було б цілком можливо створити Attributeознаку в просторі модуля, щось подібне is settable, що створило б метод альтернативного аксесуара, який би прийняв єдине значення для встановлення значення. Проблема з цим в основному полягає в тому, що я думаю, що в основному у світі існує два табори щодо зворотної вартості такого мутатора: чи поверне це нове значення чи старе значення?

Будь ласка, зв'яжіться зі мною, якщо ви зацікавлені в застосуванні такої ознаки в просторі модулів.


1
Дякую @Elizabeth - це цікавий куточок - я торкаюся цього питання лише тут і там, і не вистачає окупності у формуванні риси, щоб зробити трюк (або вміння з мого боку). Я справді намагався обробити найкращу практику кодування та підходити до цього - і сподівався, що дизайн Раку буде узгоджений з найкращими практиками, які я знаю.
p6steve

6

Я зараз підозрюю, що ви просто заплуталися. 1 Перш ніж торкнутися цього, почнемо спочатку з того, що вас не бентежить:

Мені подобається безпосередність виконання =завдання, але мені потрібна легкість втручатися в побічні дії, які надають різні методи. ... Я не розумію, чому я не можу просто піти $c.v( 43 )Щоб встановити загальнодоступний атрибут

Ви можете зробити все це. Тобто, ви використовуєте =присвоєння, кілька методів і "просто йдіть $c.v( 43 )", і все одночасно, якщо ви хочете:

class C {
  has $!v;
  multi method v                is rw {                  $!v }
  multi method v ( :$trace! )   is rw { say 'trace';     $!v }
  multi method v ( $new-value )       { say 'new-value'; $!v = $new-value }
}
my $c = C.new;
$c.v = 41;
say $c.v;            # 41
$c.v(:trace) = 42;   # trace
say $c.v;            # 42
$c.v(43);            # new-value
say $c.v;            # 43

Можливе джерело плутанини 1

За лаштунками has $.foo is rwгенерує атрибут та єдиний метод у рядках:

has $!foo;
method foo () is rw { $!foo }

Сказане не зовсім правильно. Зважаючи на поведінку, яку ми бачимо, автогенерований fooметод компілятора якось оголошується таким чином, що будь-який новий метод з такою ж назвою мовчки затінює його. 2

Отже, якщо ви хочете, щоб один або кілька спеціальних методів з тим самим іменем, як атрибут, ви повинні вручну копіювати автоматично створений метод, якщо ви хочете зберегти поведінку, за яку зазвичай несе відповідальність.

Виноски

1 Див. Відповідь jnthn для чіткого, ретельного, авторитетного відображення думки Раку про приватних проти публічних геттерів / сеттерів та про те, що вона робить за кадром, коли ви заявляєте про публічних геттерів / сеттерів (тобто пишете has $.foo).

2 Якщо був оголошений метод автоматичної генерації аксесуара для атрибута only, то, вважаю, Раку видасть виняток, якщо оголошено метод з тим самим іменем. Якби це було оголошено multi, то воно не повинно бути затіненим, якщо новий метод також був оголошений multi, і він повинен викинути виняток, якщо ні. Таким чином, автогенерований аксесуар оголошується ні, onlyні, multiале натомість певним чином, що дозволяє беззвучне затінення.


Ага - спасибі @raiph - це те, чого я відчував, пропав. Тепер це має сенс. На Jnthn я, мабуть, спробую бути кращим справжнім кодером OO і зберегти стиль сеттера для чистого контейнера даних. Але добре знати, що це в панелі інструментів!
p6steve
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.