Mieszanie prywatnych i publicznych atrybutów i akcesoriów w 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)

jak dotąd, tak rozsądnie, a potem

#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

Podoba mi się natychmiastowość przypisania „=”, ale potrzebuję łatwości łączenia działań bocznych zapewnianych przez wiele metod. Rozumiem, że są to dwa różne światy i że się nie mieszają.

ALE - nie rozumiem, dlaczego nie mogę po prostu przejść $ cv (43) Aby ustawić atrybut publiczny

  1. Wydaje mi się, że raku prowadzi mnie do nie mieszania tych dwóch trybów - niektórych atrybutów prywatnych i niektórych publicznych, i że presja jest skierowana na metodę metody (z niektórymi: cukier z okrężnicy) - czy taki jest zamysł projektu Raku?
  2. Czy coś brakuje?
p6steve
źródło
Możesz użyć proxy
user0721090601
Jak możesz użyć proxy? Chodzi mi o to, że akcesorium już zwraca kontener, gdy is rwjest określony. Zwrócenie serwera proxy nie spowoduje zmiany liczby dopuszczalnych parametrów na akcesorium.
Elizabeth Mattijsen
@ElizabethMattijsen Być może źle zrozumiałem pytanie. Ale wydaje się, że działa to na to, czego chce (włączanie obu = fooi .(foo)ustawianie) i umożliwianie działań niepożądanych w obu przypadkach (ale nie tylko po pobraniu
user0721090601

Odpowiedzi:

13

czy taka jest intencja projektu Raku?

Można śmiało powiedzieć, że Raku nie jest całkowicie pozbawiony ludzi w tej dziedzinie. Twoje pytanie dotyczy dwóch tematów w projekcie Raku, które są warte krótkiej dyskusji.

Raku ma pierwszorzędne wartości l

Raku obficie wykorzystuje wartości l, które są pierwszorzędne. Kiedy piszemy:

has $.x is rw;

Generowana metoda to:

method x() is rw { $!x }

is rwTutaj wskazuje, że metoda jest zwrócenie l-wartość - to znaczy, że coś może być przypisany. Kiedy więc piszemy:

$obj.x = 42;

To nie jest cukier składniowy: tak naprawdę jest to wywołanie metody, a następnie do jego wyniku stosuje się operator przypisania. Działa to, ponieważ wywołanie metody zwraca Scalarkontener atrybutu, do którego można następnie przypisać. Można użyć wiązania, aby podzielić to na dwa kroki, aby przekonać się, że nie jest to trywialna transformacja składniowa. Na przykład:

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

Byłoby przypisanie do atrybutu obiektu. Ten sam mechanizm kryje się za wieloma innymi funkcjami, w tym przypisywaniem list. Na przykład:

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

Polega na konstruowaniu Listzawierające pojemniki $xi $y, a następnie operator przypisania w tym przypadku parami iteracje każdym boku do wykonania zadania. Oznacza to, że możemy tam użyć rwakcesoriów do obiektów:

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

I to wszystko po prostu działa naturalnie. Jest to również mechanizm przypisywania do wycinków tablic i skrótów.

Można również użyć Proxydo utworzenia kontenera o wartości l, w którym zachowanie odczytu i zapisu jest pod twoją kontrolą. W ten sposób możesz umieścić działania poboczne STORE. Jednak...

Raku zachęca metody semantyczne do „seterów”

Kiedy opisujemy OO, często pojawiają się terminy takie jak „enkapsulacja” i „ukrywanie danych”. Kluczową ideą jest to, że model stanu wewnątrz obiektu - to znaczy sposób, w jaki wybiera on reprezentację danych potrzebnych do wdrożenia swoich zachowań (metod) - może ewoluować, na przykład w celu sprostania nowym wymaganiom. Im bardziej złożony obiekt, tym bardziej staje się on wyzwalający.

Jednak metody pobierające i ustawiające są metodami, które mają niejawne połączenie ze stanem. Chociaż możemy twierdzić, że osiągamy ukrywanie danych, ponieważ wywołujemy metodę, nie uzyskując bezpośredniego dostępu do stanu, z mojego doświadczenia wynika, że ​​szybko kończymy w miejscu, w którym kod zewnętrzny wykonuje sekwencje wywołań ustawiających w celu osiągnięcia operacji - która jest forma anty-wzorca zazdrości funkcji. A jeśli robimy to , to całkiem pewne, będziemy skończyć z logicznego zewnątrz obiektu, który robi mieszankę getter i setter operacji osiągnąć operację. Naprawdę, operacje te powinny były zostać ujawnione jako metody o nazwach opisujących osiągane cele. Staje się to jeszcze ważniejsze, jeśli jesteśmy w równoległym otoczeniu; dobrze zaprojektowany obiekt jest często dość łatwy do ochrony na granicy metody.

To powiedziawszy, wiele zastosowań classjest naprawdę typami rekordów / produktów: istnieją po prostu zgrupować kilka elementów danych. To nie przypadek, że .sigil nie tylko generuje akcesor, ale także:

  • Określa, że ​​atrybut jest ustawiany przez domyślną logikę inicjowania obiektu (to znaczy class Point { has $.x; has $.y; }można utworzyć instancję jako Point.new(x => 1, y => 2)), a także renderuje to w .rakumetodzie zrzutu.
  • Zamienia atrybut w domyślny .Captureobiekt, co oznacza, że ​​możemy go użyć do destrukcji (np sub translated(Point (:$x, :$y)) { ... }.).

Czego chciałbyś, gdybyś pisał w bardziej proceduralny lub funkcjonalny sposób i używał classjako środka do zdefiniowania typu rekordu.

Projekt Raku nie jest zoptymalizowany do robienia sprytnych rzeczy w seterach, ponieważ uważa się to za kiepską rzecz do optymalizacji. Wykracza to poza to, co jest potrzebne do typu rekordu; w niektórych językach moglibyśmy argumentować, że chcemy dokonać weryfikacji tego, co zostało przypisane, ale w Raku możemy do tego użyć subsettypów. Jednocześnie, jeśli naprawdę projektujemy OO, to chcemy interfejsu API znaczących zachowań, który ukrywa model stanu, zamiast myśleć w kategoriach pobierających / ustawiających, co zwykle prowadzi do niepowodzenia kolokacji dane i zachowanie, co zresztą w dużej mierze ma sens w OO.

Jonathan Worthington
źródło
Dobra uwaga na przestroga Proxy(nawet jeśli zasugerowałem to ha). Jedyny raz, kiedy uznałem je za niezwykle przydatne, jest dla mnie LanguageTag. Wewnętrznie $tag.regionzwraca obiekt typu Region(jak jest przechowywany wewnętrznie), ale w rzeczywistości jest to o wiele bardziej wygodne dla ludzi, aby powiedzieć $tag.region = "JP"na $tag.region.code = "JP". A to naprawdę tylko tymczasowe, dopóki nie będę mógł wyrazić przymusu z Strtego typu, np. has Region(Str) $.region is rw(Który wymaga dwóch osobnych funkcji planowanych, ale o niskim priorytecie)
user0721090601
Dziękuję @Jathanathan za poświęcenie czasu na opracowanie uzasadnienia projektowego - podejrzewałem jakiś aspekt oleju i wody, a za bodę inną niż CS, jak ja, naprawdę dostaję rozróżnienie między właściwym OO z ukrytym stanem a zastosowaniem klas es jako bardziej przyjazny sposób na budowanie ezoterycznych posiadaczy danych, którzy otrzymaliby stopień doktora w C ++. I do user072 ... za twoje przemyślenia na temat proxy. Wcześniej wiedziałem o serwerach proxy, ale podejrzewałem, że mają one (i / lub cechy) celowo dość uciążliwą składnię, aby zniechęcić do mieszania oleju i wody ...
p6steve
p6steve: Raku został tak naprawdę zaprojektowany, aby uczynić najczęstsze / najłatwiejsze rzeczy super łatwymi. Kiedy odejdziesz od typowych modeli, zawsze jest to możliwe (myślę, że widziałeś do tej pory trzy różne sposoby robienia tego, co chcesz ... i jest zdecydowanie więcej), ale to sprawia, że ​​pracujesz trochę - wystarczy, aby zrobić pewnie to, co robisz, naprawdę tego chcesz. Ale dzięki cechom, serwerom proxy, slangom itp. Możesz to zrobić, więc potrzebujesz tylko kilku dodatkowych znaków, aby naprawdę włączyć fajne rzeczy, gdy ich potrzebujesz.
user0721090601
7

ALE - nie rozumiem, dlaczego nie mogę po prostu przejść $ cv (43) Aby ustawić atrybut publiczny

Cóż, to naprawdę zależy od architekta. Ale tak na poważnie, nie, to po prostu nie jest standardowy sposób działania Raku.

Teraz byłoby całkowicie możliwe stworzenie Attributecechy w przestrzeni modułów, coś w rodzaju is settable, co stworzyłoby alternatywną metodę akcesorium, która zaakceptowałaby pojedynczą wartość w celu ustawienia wartości. Problem z robieniem tego w istocie polega na tym, że myślę, że na świecie istnieją zasadniczo 2 obozy dotyczące wartości zwrotu takiego mutatora: czy zwróciłoby nową wartość, czy starą wartość?

Proszę o kontakt, jeśli jesteś zainteresowany wdrożeniem takiej cechy w przestrzeni modułów.

Elizabeth Mattijsen
źródło
1
Dzięki @Elizabeth - to jest interesujący kąt - uderzam tylko to pytanie tu i tam i nie ma wystarczającego zwrotu w budowaniu cechy, aby wykonać lewę (lub umiejętność z mojej strony). Naprawdę starałem się ominąć najlepszą praktykę kodowania i dostosować się do niej - i mam nadzieję, że projekt Raku będzie dostosowany do najlepszych praktyk, które, jak rozumiem, są.
p6steve
6

Podejrzewam, że właśnie się zdezorientowałeś. 1 Zanim dotknę tego, zacznijmy od tego, o czym się nie mylisz:

Podoba mi się natychmiastowość tego =zadania, ale potrzebuję łatwości natarcia w czynnościach ubocznych, które zapewnia wiele metod. ... Nie rozumiem, dlaczego nie mogę tak po prostu $c.v( 43 )ustawić atrybutu publicznego

Państwo może zrobić wszystkie te rzeczy. To znaczy, że używasz =przypisania i wielu metod, i „po prostu idź $c.v( 43 )”, wszystko w tym samym czasie, jeśli chcesz:

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

Możliwe źródło zamieszania 1

Za kulisami has $.foo is rwgeneruje atrybut i pojedynczą metodę w następujący sposób:

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

Powyższe nie jest jednak właściwe. Biorąc pod uwagę zachowanie, które obserwujemy, autogenerowana foometoda kompilatora jest w jakiś sposób deklarowana w taki sposób, że każda nowa metoda o tej samej nazwie cicho ją zacienia . 2)

Jeśli więc chcesz mieć jedną lub więcej niestandardowych metod o tej samej nazwie jako atrybut, musisz ręcznie zreplikować automatycznie wygenerowaną metodę, jeśli chcesz zachować zachowanie, za które normalnie byłaby odpowiedzialna.

Przypisy

1 Zobacz odpowiedź jnthna, aby uzyskać jasne, dokładne i autorytatywne rozliczenie opinii Raku na temat prywatnych / publicznych osób pobierających / ustawiających i tego, co robi za kulisami, kiedy ogłaszasz publiczne osoby pobierające / ustawiające (tj. Pisz has $.foo).

2 Jeśli zostanie automatycznie zadeklarowana metoda akcesorium dla atrybutu only, to Raku, jak sądzę, rzuci wyjątek, jeśli zadeklarowana zostanie metoda o tej samej nazwie. Jeśli został zadeklarowany multi, nie powinien być przesłaniany, jeśli nowa metoda została zadeklarowana multi, i powinien zgłosić wyjątek, jeśli nie. Tak więc autogenerowane akcesorium jest zadeklarowane za pomocą ani onlyani multizamiast, ale w jakiś sposób, który pozwala na ciche cieniowanie.

raiph
źródło
Aha - dzięki @raiph - tego właśnie brakowało mi. Teraz to ma sens. Na Jnthna zapewne postaram się być lepszym koderem OO i zachować styl seterów dla czystych kontenerów danych. Ale dobrze wiedzieć, że jest to w zestawie narzędzi!
p6steve