Смешивание частных и публичных атрибутов и аксессоров в Raku

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. Я что-то пропустил?
p6steve
источник
Вы можете использовать прокси
user0721090601
Как вы могли бы использовать прокси? Я имею в виду, средство доступа уже возвращает контейнер, когда is rwуказано. Возврат прокси не приведет к изменению количества допустимых параметров в методе доступа.
Элизабет Маттийсен
@ ElizabethMattijsen Возможно, я неправильно понял вопрос. Но, похоже, это работает для того, что он хочет= foo и .(foo)настройку, и включение), и побочные эффекты могут быть выполнены в обоих случаях (но не только при извлечении): tio.run/…
user0721090601

Ответы:

13

в этом ли смысл замысла Раку?

Справедливо сказать, что Раку не совсем незавершен в этой области. Ваш вопрос затрагивает две темы в дизайне Raku, которые стоит немного обсудить.

Раку имеет первоклассные 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действительно являются типами записей / продуктов: они существуют, чтобы просто сгруппировать кучу элементов данных. Не случайно, что .сигил не только генерирует аксессор, но и:

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

Что бы вы хотели, если бы вы писали в более процедурном или функциональном стиле и использовали его classкак средство для определения типа записи.

Дизайн Raku не оптимизирован для умных действий в сеттерах, потому что это плохо для оптимизации. Это выше того, что нужно для типа записи; на некоторых языках мы можем утверждать, что хотим сделать проверку того, что присваивается, но в Raku мы можем обратиться к subsetтипам для этого. В то же время, если мы действительно занимаемся проектированием ОО, нам нужен API осмысленного поведения, которое скрывает модель состояния, а не думать с точки зрения геттеров / сеттеров, что, как правило, приводит к невозможности совместного размещения данные и поведение, что в любом случае является основной целью ОО.

Джонатан Уортингтон
источник
Хороший момент, чтобы предупредить Proxys (хотя я и предложил это ха). Единственный раз, когда я нашел их очень полезными для меня LanguageTag. Внутренне $tag.regionвозвращает объект типа Region(как он хранится внутри), но реальность такова, что это гораздо более удобно для людей , чтобы сказать $tag.region = "JP"более $tag.region.code = "JP". И это на самом деле только временно, пока я не смогу выразить приведение Strв типе, например, has Region(Str) $.region is rw(который требует двух отдельных запланированных, но низкоприоритетных функций)
user0721090601
Спасибо @Jonathan за то, что вы нашли время для разработки обоснования дизайна - я подозревал какой-то аспект нефти и воды, и для такого тела, не являющегося CS, как я, я действительно получаю различие между правильным ОО со скрытым состоянием и применением классов es как более дружественный способ создания эзотерических носителей данных, которые позволят получить степень доктора наук в C ++ И пользователю 072 ... за ваши мысли о Proxy s. Я и раньше знал о Proxy, но подозревал, что они (и / или черты) являются преднамеренно обременительным синтаксисом, препятствующим смешиванию масла и воды ...
p6steve
p6steve: Raku действительно был разработан, чтобы сделать самые простые / простые вещи супер легкими. Когда вы отклоняетесь от общих моделей, это всегда возможно (я думаю, что вы уже видели три разных способа сделать то, что вы хотите до сих пор ... и, безусловно, есть и другие), но это заставляет вас работать немного - просто достаточно, чтобы сделать уверен, что ты действительно хочешь, что хочешь. Но с помощью черт, прокси, сленга и т. Д. Вы можете сделать это так, что вам нужно всего лишь несколько дополнительных символов, чтобы действительно включить некоторые интересные вещи, когда вам это нужно.
user0721090601
7

НО - я не понимаю, почему я не могу просто пойти $ cv (43), чтобы установить публичный атрибут

Ну, это действительно зависит от архитектора. А если серьезно, нет, это просто не стандартный способ работы Раку.

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

Пожалуйста, свяжитесь со мной, если вы заинтересованы в реализации такой черты в модульном пространстве.

Элизабет Маттийсен
источник
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, то Raku, я полагаю, сгенерирует исключение, если будет объявлен метод с тем же именем. Если он был объявлен multi, то он не должен быть скрыт, если новый метод также был объявлен multi, и должен генерировать исключение, если нет. Таким образом, автоматически сгенерированный метод доступа объявляется ни с помощью, ни с чем- onlyлибо, multiно позволяет каким-либо образом скрывать тени.

raiph
источник
Ага - спасибо @raiph - это то, чего я чувствовал, не хватало. Теперь это имеет смысл. Вероятно, я постараюсь быть лучшим настоящим OO-кодером и сохранить стиль установки для контейнеров с чистыми данными. Но приятно знать, что это в наборе инструментов!
p6steve