Mischen von privaten und öffentlichen Attributen und Accessoren in 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)

so weit, so vernünftig und dann

#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

Ich mag die Unmittelbarkeit der Zuweisung '=', aber ich brauche die Leichtigkeit, Nebenaktionen durchzuführen, die mehrere Methoden bieten. Ich verstehe, dass dies zwei verschiedene Welten sind und dass sie sich nicht vermischen.

ABER - Ich verstehe nicht, warum ich nicht einfach $ cv (43) gehen kann, um ein öffentliches Attribut festzulegen

  1. Ich habe das Gefühl, dass Raku mich dazu anleitet, diese beiden Modi nicht zu mischen - einige Attribute privat und andere öffentlich und dass der Druck auf die Methodenmethode gerichtet ist (mit einigen: Zucker aus dem Dickdarm) - ist dies die Absicht von Rakus Design?
  2. Vermisse ich etwas
p6steve
quelle
Sie könnten einen Proxy verwenden
user0721090601
Wie können Sie einen Proxy verwenden? Ich meine, der Accessor gibt bereits einen Container zurück, wenn er is rwangegeben ist. Durch die Rückgabe eines Proxys wird die Anzahl der zulässigen Parameter auf dem Accessor nicht geändert.
Elizabeth Mattijsen
@ ElizabethMattijsen Vielleicht habe ich die Frage falsch verstanden. Aber dies scheint für das zu funktionieren, was er will (sowohl aktivieren = fooals auch .(foo)einstellen) und in beiden Fällen Nebenwirkungen zu ermöglichen (aber nicht, wenn nur abgerufen): tio.run/…
user0721090601

Antworten:

13

Ist das die Absicht von Rakus Design?

Es ist fair zu sagen, dass Raku in diesem Bereich nicht völlig unbefangen ist. Ihre Frage berührt zwei Themen in Rakus Design, die beide eine kleine Diskussion wert sind.

Raku hat erstklassige l-Werte

Raku nutzt L-Werte in Hülle und Fülle als erstklassige Sache. Wenn wir schreiben:

has $.x is rw;

Die generierte Methode ist:

method x() is rw { $!x }

Das is rwhier zeigt an, dass die Methode einen l-Wert zurückgibt, dh etwas, dem zugewiesen werden kann. Wenn wir also schreiben:

$obj.x = 42;

Dies ist kein syntaktischer Zucker: Es ist wirklich ein Methodenaufruf, und dann wird der Zuweisungsoperator auf das Ergebnis angewendet. Dies funktioniert, da der Methodenaufruf den ScalarContainer des Attributs zurückgibt , dem dann zugewiesen werden kann. Man kann die Bindung verwenden, um dies in zwei Schritte aufzuteilen, um zu sehen, dass es sich nicht um eine triviale syntaktische Transformation handelt. Zum Beispiel:

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

Würde dem Objektattribut zuweisen. Derselbe Mechanismus steckt hinter zahlreichen anderen Funktionen, einschließlich der Listenzuweisung. Zum Beispiel:

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

Konstruiert ein Konstrukt, Listdas die Container $xund enthält $y, und dann iteriert der Zuweisungsoperator in diesem Fall jede Seite paarweise, um die Zuweisung durchzuführen. Dies bedeutet, dass wir dort rwObjekt-Accessoren verwenden können:

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

Und alles funktioniert ganz natürlich. Dies ist auch der Mechanismus für die Zuweisung von Arrays und Hashes.

Sie können auch Proxyeinen L-Wert-Container erstellen, in dem Sie das Lese- und Schreibverhalten steuern können. So können Sie die Nebenaktionen in setzen STORE. Jedoch...

Raku fördert semantische Methoden gegenüber "Setzern"

Wenn wir OO beschreiben, tauchen häufig Begriffe wie "Kapselung" und "Verstecken von Daten" auf. Die Schlüsselidee dabei ist, dass sich das Zustandsmodell innerhalb des Objekts - dh die Art und Weise, wie es die Daten darstellt, die es zur Implementierung seines Verhaltens (der Methoden) benötigt - frei entwickeln kann, beispielsweise um neue Anforderungen zu bewältigen. Je komplexer das Objekt ist, desto befreiender wird es.

Getter und Setter sind jedoch Methoden, die eine implizite Verbindung zum Status haben. Während wir vielleicht behaupten, dass wir das Ausblenden von Daten erreichen, weil wir eine Methode aufrufen und nicht direkt auf den Status zugreifen, ist meine Erfahrung, dass wir schnell an einem Ort landen, an dem externer Code Sequenzen von Setter-Aufrufen ausführt, um eine Operation zu erreichen - das heißt eine Form des Merkmals Neid Anti-Muster. Und wenn wir das tun , ist es ziemlich sicher, dass wir eine Logik außerhalb des Objekts haben, die eine Mischung aus Getter- und Setter-Operationen ausführt, um eine Operation zu erreichen. Eigentlich sollten diese Operationen als Methoden mit einem Namen verfügbar gemacht worden sein, der beschreibt, was erreicht wird. Dies wird noch wichtiger, wenn wir uns in einer gleichzeitigen Umgebung befinden. Ein gut gestaltetes Objekt ist an der Methodengrenze oft recht einfach zu schützen.

Das heißt, viele Verwendungen von classsind wirklich Datensatz- / Produkttypen: Sie existieren, um einfach eine Reihe von Datenelementen zu gruppieren. Es ist kein Zufall, dass das .Siegel nicht nur einen Accessor generiert, sondern auch:

  • Legt fest, dass das Attribut durch die Standardobjektinitialisierungslogik festgelegt wird (dh a class Point { has $.x; has $.y; }kann als instanziiert werden Point.new(x => 1, y => 2)), und rendert dies auch in der .rakuDumpingmethode.
  • Fügt das Attribut in das Standardobjekt ein .Capture, was bedeutet, dass wir es bei der Destrukturierung verwenden können (z sub translated(Point (:$x, :$y)) { ... }. B. ).

Welches sind die Dinge, die Sie möchten, wenn Sie in einem prozeduraleren oder funktionaleren Stil schreiben und classals Mittel zum Definieren eines Datensatztyps verwenden würden.

Das Raku-Design ist nicht für clevere Dinge in Setzern optimiert, da dies als schlecht zu optimierend angesehen wird. Es geht über das hinaus, was für einen Datensatztyp benötigt wird. In einigen Sprachen könnten wir argumentieren, dass wir die zugewiesenen Aufgaben validieren möchten, aber in Raku können wir uns dafür subsetTypen zuwenden . Wenn wir wirklich ein OO-Design erstellen, möchten wir gleichzeitig eine API mit aussagekräftigen Verhaltensweisen, die das Zustandsmodell verbirgt, anstatt in Getter / Setter zu denken, die dazu führen, dass die Zuordnung fehlschlägt Daten und Verhalten, was ohnehin ein wichtiger Punkt bei OO ist.

Jonathan Worthington
quelle
Guter Punkt, um das Proxys zu warnen (obwohl ich es vorgeschlagen habe ha). Das einzige Mal, dass ich sie für schrecklich nützlich befunden habe, ist für mich LanguageTag. Intern wird die $tag.regiongibt ein Objekt vom Typ Region(wie es intern gespeichert ist), aber die Realität ist, es ist unendlich viel bequemer für die Menschen zu sagen , $tag.region = "JP"über $tag.region.code = "JP". Und das ist wirklich nur vorübergehend, bis ich einen Zwang aus Strdem Typ ausdrücken kann , z. B. has Region(Str) $.region is rw(der zwei separate geplante Funktionen mit niedriger Priorität
erfordert
Vielen Dank an Jonathan, dass Sie sich die Zeit genommen haben, um die Gründe für das Design zu erläutern. Ich hatte einen Öl- und Wasseraspekt vermutet und für einen Nicht-CS-Körper wie mich bekomme ich wirklich die Unterscheidung zwischen richtigem OO mit verstecktem Zustand und der Anwendung von Klassen als Ein freundlicherer Weg, um esoterische Dateninhaber zu erstellen, die in C ++ promovieren würden. Und an user072 ... für Ihre Gedanken zu Proxy s. Ich wusste schon früher über Proxys Bescheid, vermutete aber, dass sie (und / oder Merkmale) absichtlich eine ziemlich belastende Syntax sind, um das Mischen von Öl und Wasser zu verhindern ...
p6steve
p6steve: Raku wurde wirklich entwickelt, um die häufigsten / einfachsten Dinge super einfach zu machen. Wenn Sie von den gängigen Modellen abweichen, ist dies immer möglich (ich glaube, Sie haben bisher drei verschiedene Möglichkeiten gesehen, um das zu tun, was Sie wollen ... und es gibt definitiv mehr), aber Sie arbeiten ein bisschen - gerade genug, um es zu machen Sicher, was Sie tun, ist wirklich wollen, dass Sie wollen. Aber über Eigenschaften, Proxies, Slangs usw. können Sie es so machen, dass Sie nur ein paar zusätzliche Charaktere benötigen, um wirklich ein paar coole Sachen zu aktivieren, wenn Sie es brauchen.
user0721090601
7

ABER - Ich verstehe nicht, warum ich nicht einfach $ cv (43) gehen kann, um ein öffentliches Attribut festzulegen

Nun, das liegt wirklich am Architekten. Aber im Ernst, nein, das ist einfach nicht die Standardarbeitsweise von Raku.

Jetzt wäre es durchaus möglich, ein AttributeMerkmal im Modulraum zu is settableerstellen, etwa eine alternative Zugriffsmethode, die einen einzelnen Wert zum Festlegen des Werts akzeptiert. Das Problem dabei ist, dass es meiner Meinung nach im Grunde zwei Lager auf der Welt gibt, in denen es um den Rückgabewert eines solchen Mutators geht: Würde er den neuen Wert oder den alten Wert zurückgeben?

Bitte kontaktieren Sie mich, wenn Sie daran interessiert sind, ein solches Merkmal im Modulraum zu implementieren.

Elizabeth Mattijsen
quelle
1
Danke @Elizabeth - das ist ein interessanter Blickwinkel - ich treffe diese Frage nur hier und da und es gibt nicht genug Amortisation, wenn ich ein Merkmal baue, um den Trick (oder meine Fähigkeiten) auszuführen. Ich habe wirklich versucht, mich mit der besten Codierungspraxis vertraut zu machen und mich darauf auszurichten - und gehofft, dass Rakus Design auf die besten Praktiken abgestimmt ist, die ich zusammenfasse.
p6steve
6

Ich vermute derzeit, dass Sie gerade verwirrt wurden. 1 Bevor ich darauf eingehe, beginnen wir noch einmal mit dem, worüber Sie nicht verwirrt sind:

Ich mag die Unmittelbarkeit der =Aufgabe, aber ich brauche die Leichtigkeit, Nebenaktionen durchzuführen, die mehrere Methoden bieten. ... Ich verstehe nicht, warum ich nicht einfach gehen kann, $c.v( 43 )um ein öffentliches Attribut festzulegen

Sie können all diese Dinge tun. Das heißt, Sie verwenden =Zuweisung und mehrere Methoden und "gehen einfach $c.v( 43 )" gleichzeitig, wenn Sie möchten:

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

Eine mögliche Quelle der Verwirrung 1

has $.foo is rwGeneriert hinter den Kulissen ein Attribut und eine einzelne Methode wie folgt:

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

Das obige ist jedoch nicht ganz richtig. Angesichts des Verhaltens, das wir sehen, wird die automatisch generierte fooMethode des Compilers irgendwie so deklariert, dass jede neue Methode mit demselben Namen sie stillschweigend beschattet . 2

Wenn Sie also eine oder mehrere benutzerdefinierte Methoden mit demselben Namen wie ein Attribut möchten, müssen Sie die automatisch generierte Methode manuell replizieren, wenn Sie das Verhalten beibehalten möchten, für das sie normalerweise verantwortlich ist.

Fußnoten

1 Siehe jnthns Antwort für eine klare, gründliche und maßgebliche Darstellung von Rakus Meinung über private und öffentliche Getter / Setter und was es hinter den Kulissen tut, wenn Sie öffentliche Getter / Setter deklarieren (dh schreiben has $.foo).

2 Wenn eine automatisch generierte Accessormethode für ein Attribut deklariert wurde only, würde Raku vermutlich eine Ausnahme auslösen, wenn eine Methode mit demselben Namen deklariert würde. Wenn es deklariert wurde multi, sollte es nicht beschattet werden, wenn die neue Methode ebenfalls deklariert wurde multi, und sollte eine Ausnahme auslösen, wenn nicht. Daher wird der automatisch generierte Accessor weder mit onlynoch deklariert, multisondern auf eine Weise, die eine stille Abschattung ermöglicht.

Raiph
quelle
Aha - danke @raiph - das ist das, was mir gefehlt hat. Jetzt macht es Sinn. Per Jnthn werde ich wahrscheinlich versuchen, ein besserer echter OO-Codierer zu sein und den Setter-Stil für reine Datencontainer beizubehalten. Aber es ist gut zu wissen, dass dies in der Toolbox ist!
p6steve