Miscelazione di attributi e accessori privati ​​e pubblici 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)

finora, così ragionevole, e poi

#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

Mi piace l'immediatezza dell'assegnazione '=', ma ho bisogno della facilità di interromperti nelle azioni collaterali fornite dai multi metodi. Capisco che questi sono due mondi diversi e che non si mescolano.

MA - Non capisco perché non posso semplicemente andare $ cv (43) per impostare un attributo pubblico

  1. Sento che Raku mi sta guidando a non mescolare queste due modalità - alcuni attributi privati ​​e altri pubblici e che la pressione è verso il metodo del metodo (con alcuni: zucchero dal colon) - è questo l'intento del design di Raku?
  2. Mi sto perdendo qualcosa?

È possibile utilizzare un proxy
user0721090601

Come hai potuto usare un proxy? Voglio dire, l'accessor restituisce già un contenitore quando is rwviene specificato. La restituzione di un proxy non cambierà il numero di parametri consentiti sull'accessor.
Elizabeth Mattijsen,

@ElizabethMattijsen Forse ho frainteso la domanda. Ma questo sembra funzionare per quello che vuole (abilitare entrambi = fooe .(foo)per l'impostazione) e abilitare gli effetti collaterali in entrambi i casi (ma non solo quando recuperati): tio.run/…
user0721090601

Risposte:


13

è questo l'intento del design di Raku?

È giusto dire che Raku non è del tutto impopolare in quest'area. La tua domanda tocca due temi nel design di Raku, che meritano entrambi una piccola discussione.

Raku ha valori l di prima classe

Raku fa un uso abbondante dei valori l come una cosa di prima classe. Quando scriviamo:

has $.x is rw;

Il metodo che viene generato è:

method x() is rw { $!x }

Il is rwqui indica che il metodo restituisce un l-value - cioè, qualcosa che può essere assegnato. Quindi quando scriviamo:

$obj.x = 42;

Questo non è zucchero sintattico: è davvero una chiamata di metodo e quindi l'operatore di assegnazione viene applicato al risultato. Questo funziona, perché la chiamata del metodo restituisce il Scalarcontenitore dell'attributo, che può quindi essere assegnato. Si può usare l'associazione per dividerlo in due passaggi, per vedere che non è una banale trasformazione sintattica. Ad esempio, questo:

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

Assegnerebbe all'attributo oggetto. Questo stesso meccanismo è alla base di numerose altre funzionalità, inclusa l'assegnazione di elenchi. Ad esempio, questo:

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

Funziona costruendo un Listcontenente i contenitori $xe $y, quindi, l'operatore di assegnazione in questo caso esegue l'iterazione di ciascun lato in modo da eseguire l'assegnazione. Questo significa che possiamo usare gli rwaccessor agli oggetti lì:

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

E tutto funziona in modo naturale. Questo è anche il meccanismo alla base dell'assegnazione a fette di array e hash.

Si può anche usare Proxyper creare un contenitore di valore l in cui il comportamento della lettura e della scrittura è sotto il tuo controllo. Pertanto, potresti mettere in atto le azioni collaterali STORE. Però...

Raku incoraggia i metodi semantici rispetto ai "setter"

Quando descriviamo OO, spesso emergono termini come "incapsulamento" e "nascondere i dati". L'idea chiave qui è che il modello di stato all'interno dell'oggetto, ovvero il modo in cui sceglie di rappresentare i dati necessari per implementare i suoi comportamenti (i metodi), è libero di evolversi, ad esempio per gestire i nuovi requisiti. Più complesso è l'oggetto, più questo diventa liberatorio.

Tuttavia, getter e setter sono metodi che hanno una connessione implicita con lo stato. Mentre potremmo affermare che stiamo ottenendo il nascondimento dei dati perché stiamo chiamando un metodo, non accedendo direttamente allo stato, la mia esperienza è che finiamo rapidamente in un luogo in cui il codice esterno sta eseguendo sequenze di chiamate del setter per realizzare un'operazione - che è una forma della caratteristica invidia anti-pattern. E se stiamo facendo che , è abbastanza certo che finiremo con la logica di fuori dell'oggetto che fa un mix di operazioni di getter e setter per ottenere un'operazione. Davvero, queste operazioni avrebbero dovuto essere esposte come metodi con nomi che descrivono ciò che si sta realizzando. Questo diventa ancora più importante se ci troviamo in una situazione simultanea; un oggetto ben progettato è spesso abbastanza facile da proteggere al limite del metodo.

Detto questo, molti usi classsono tipi di record / prodotti: esistono semplicemente per raggruppare un insieme di elementi di dati. Non è un caso che il .sigillo non generi solo un accessorio, ma anche:

  • Opta l'attributo come impostato dalla logica di inizializzazione dell'oggetto predefinita (ovvero, a class Point { has $.x; has $.y; }può essere istanziato come Point.new(x => 1, y => 2)) e lo rende anche nel .rakumetodo di dumping.
  • Opta l'attributo .Capturenell'oggetto predefinito , il che significa che possiamo usarlo nella destrutturazione (ad es sub translated(Point (:$x, :$y)) { ... }.).

Quali sono le cose che vorresti se stessi scrivendo in uno stile più procedurale o funzionale e usando class come mezzo per definire un tipo di record.

Il design Raku non è ottimizzato per fare cose intelligenti nei setter, perché è considerato una cosa scadente per cui ottimizzare. È oltre ciò che è necessario per un tipo di record; in alcune lingue potremmo sostenere di voler fare la validazione di ciò che viene assegnato, ma in Raku possiamo rivolgerci ai subsettipi per quello. Allo stesso tempo, se stiamo davvero facendo una progettazione OO, allora vogliamo un'API di comportamenti significativi che nasconda il modello statale, piuttosto che pensare in termini di getter / setter, che tendono a portare a un fallimento nel colocare dati e comportamento, che è comunque molto importante fare OO.


Un buon punto per avvertire la Proxys (anche se l'ho suggerito ha). L'unica volta che li ho trovati terribilmente utili è per me LanguageTag. Internamente, l' $tag.regionrestituisce un oggetto di tipo Region(come è memorizzato internamente), ma la realtà è, è infinitamente più conveniente per le persone a dire $tag.region = "JP"sopra $tag.region.code = "JP". E questo è davvero solo temporaneo fino a quando non riesco ad esprimere una coercizione Strnel tipo, ad esempio has Region(Str) $.region is rw(che richiede due funzioni separate pianificate ma a bassa priorità)
user0721090601

Grazie @Jonathan per aver dedicato del tempo all'elaborazione della logica del design - avevo sospettato un qualche tipo di aspetto di olio e acqua e per un corpo non CS come me, ho davvero la distinzione tra OO corretto con stato nascosto e l'applicazione di classi es come un modo più amichevole per costruire titolari di dati esoterici che richiederebbero un dottorato di ricerca in C ++. E a user072 ... per i tuoi pensieri su Proxy s. Prima sapevo dei Proxy ma sospettavo che essi (e / o tratti) fossero deliberatamente una sintassi abbastanza onerosa per scoraggiare la miscelazione di olio e acqua ...
p6steve

p6steve: Raku è stato davvero progettato per rendere le cose più comuni / facili super facili. Quando ti discosti dai modelli comuni, è sempre possibile (penso che tu abbia visto circa tre diversi modi per fare ciò che vuoi finora ... e ce n'è sicuramente di più), ma ti fa lavorare un po '- quanto basta certo quello che stai facendo è davvero ciò che vuoi. Ma tramite tratti, proxy, slang, ecc., Puoi farcela in modo da avere bisogno di pochi personaggi extra per abilitare davvero alcune cose interessanti quando ne hai bisogno.
user0721090601

7

MA - Non capisco perché non posso semplicemente andare $ cv (43) per impostare un attributo pubblico

Bene, dipende davvero dall'architetto. Ma seriamente, no, non è semplicemente il modo standard in cui Raku funziona.

Ora, sarebbe del tutto possibile creare un Attributetratto nello spazio del modulo, qualcosa di simile is settable, che avrebbe creato un metodo di accesso alternativo che avrebbe accettare un singolo valore per impostare il valore. Il problema nel farlo è che penso che ci siano praticamente 2 campi nel mondo sul valore di ritorno di un simile mutatore: restituirebbe il nuovo valore o il vecchio valore?

Vi prego di contattarmi se siete interessati a implementare tale tratto nello spazio del modulo.


1
Grazie @Elizabeth - questa è un'angolazione interessante - sto solo rispondendo a questa domanda qua e là e non c'è abbastanza rimborso nel costruire un tratto per fare il trucco (o l'abilità da parte mia). Stavo davvero cercando di orientarmi con le migliori pratiche di codifica e di allinearmi a questo - e sperando che il design di Raku sarebbe stato allineato alle migliori pratiche, che ritengo sia.
sabato

6

Al momento sospetto che ti sia appena confuso. 1 Prima di toccarlo, ricominciamo con ciò di cui non sei confuso:

Mi piace l'immediatezza =dell'incarico, ma ho bisogno della facilità di interromperti nelle azioni collaterali fornite dai multi metodi. ... Non capisco perché non posso semplicemente andare $c.v( 43 )a impostare un attributo pubblico

È possibile fare tutte queste cose. Vale a dire che usi il =compito, i metodi multipli e "vai $c.v( 43 )", tutto allo stesso tempo se vuoi:

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

Una possibile fonte di confusione 1

Dietro le quinte, has $.foo is rwgenera un attributo e un singolo metodo lungo le linee di:

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

Quanto sopra non è del tutto giusto però. Dato il comportamento che stiamo vedendo, il foometodo autogenerato del compilatore viene in qualche modo dichiarato in modo tale che qualsiasi nuovo metodo con lo stesso nome lo ombreggia silenziosamente .2

Quindi, se si desidera uno o più metodi personalizzati con lo stesso nome di un attributo, è necessario replicare manualmente il metodo generato automaticamente se si desidera conservare il comportamento di cui sarebbe normalmente responsabile.

Le note

1 Vedi la risposta di jnthn per una chiara, esauriente e autorevole spiegazione dell'opinione di Raku sui getter / setter privati ​​vs pubblici e su ciò che fa dietro le quinte quando dichiari getter / setter pubblici (cioè scrivi has $.foo).

2 Se viene dichiarato un metodo di accesso generato automaticamente per un attributo only, presumo che Raku lanci un'eccezione se viene dichiarato un metodo con lo stesso nome. Se è stato dichiarato multi, non dovrebbe essere ombreggiato se è stato dichiarato anche il nuovo metodo multie, in caso contrario, dovrebbe generare un'eccezione. Quindi l'accessor generatore automatico viene dichiarato senza né onlymultima in qualche modo che consente l'ombreggiamento silenzioso.


Aha - grazie @raiph - questa è la cosa che mi mancava. Adesso ha senso. Probabilmente cercherò di essere un vero codificatore OO migliore per Jnthn e mantenere lo stile setter per i contenitori di dati puri. Ma è bello sapere che questo è nella casella degli strumenti!
sabato
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.