Mezcla de atributos públicos y privados y accesorios en 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)

hasta ahora, tan razonable, y luego

#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

Me gusta la inmediatez de la asignación '=', pero necesito la facilidad de bunging en acciones paralelas que proporcionan los métodos múltiples. Entiendo que estos son dos mundos diferentes, y que no se mezclan.

PERO - No entiendo por qué no puedo ir solo $ cv (43) Para establecer un atributo público

  1. Siento que el raku me está guiando para no mezclar estos dos modos, algunos atributos privados y otros públicos, y que la presión es hacia el método del método (con algunos: azúcar del colon), ¿es esta la intención del diseño de Raku?
  2. ¿Me estoy perdiendo de algo?
p6steve
fuente
Podrías usar un Proxy
user0721090601
¿Cómo podrías usar un Proxy? Quiero decir, el is rwdescriptor de acceso ya devuelve un contenedor cuando se especifica. Devolver un Proxy no va a cambiar el número de parámetros permitidos en el descriptor de acceso.
Elizabeth Mattijsen
@ElizabethMattijsen Quizás no entendí bien la pregunta. Pero esto parece funcionar para lo que quiere (habilitar ambos = fooy .(foo)para configurar) y permitir que se hagan efectos secundarios en ambos casos (pero no solo cuando se obtienen
user0721090601

Respuestas:

13

¿Es esta la intención del diseño de Raku?

Es justo decir que Raku no es del todo indiscutible en esta área. Su pregunta toca dos temas en el diseño de Raku, que vale la pena discutir un poco.

Raku tiene valores l de primera clase

Raku hace un uso abundante de los valores l como algo de primera clase. Cuando escribimos:

has $.x is rw;

El método que se genera es:

method x() is rw { $!x }

El is rwaquí indica que el método devuelve un valor L - es decir, algo que se puede asignar a. Así cuando escribimos:

$obj.x = 42;

Esto no es azúcar sintáctica: realmente es una llamada a un método, y luego el operador de asignación se aplica al resultado de la misma. Esto funciona porque la llamada al método devuelve el Scalarcontenedor del atributo, que luego se puede asignar. Se puede usar el enlace para dividir esto en dos pasos, para ver que no es una transformación sintáctica trivial. Por ejemplo, esto:

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

Estaría asignando al atributo del objeto. Este mismo mecanismo está detrás de muchas otras características, incluida la asignación de listas. Por ejemplo, esto:

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

Funciona mediante la construcción de un Listcontenedor que contiene los contenedores $xy $y, a continuación, el operador de asignación en este caso itera cada lado por pares para hacer la asignación. Esto significa que podemos usar rwaccesores de objetos allí:

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

Y todo funciona naturalmente. Este es también el mecanismo detrás de la asignación a sectores de matrices y hashes.

También se puede usar Proxy para crear un contenedor de valor l donde el comportamiento de leerlo y escribirlo esté bajo su control. Por lo tanto, podría poner las acciones secundarias en STORE. Sin embargo...

Raku fomenta los métodos semánticos sobre los "setters"

Cuando describimos OO, a menudo aparecen términos como "encapsulación" y "ocultación de datos". La idea clave aquí es que el modelo de estado dentro del objeto, es decir, la forma en que elige representar los datos que necesita para implementar sus comportamientos (los métodos), es libre de evolucionar, por ejemplo, para manejar nuevos requisitos. Cuanto más complejo es el objeto, más liberador se vuelve.

Sin embargo, getters y setters son métodos que tienen una conexión implícita con el estado. Si bien podríamos afirmar que estamos logrando ocultar datos porque estamos llamando a un método, no accediendo al estado directamente, mi experiencia es que rápidamente terminamos en un lugar donde el código externo está haciendo secuencias de llamadas de setter para lograr una operación, que es una forma de la envidia característica antipatrón. Y si estamos haciendo eso , es bastante seguro que terminaremos con una lógica fuera del objeto que hace una combinación de operaciones getter y setter para lograr una operación. Realmente, estas operaciones deberían haberse expuesto como métodos con nombres que describen lo que se está logrando. Esto se vuelve aún más importante si estamos en un entorno concurrente; un objeto bien diseñado a menudo es bastante fácil de proteger en el límite del método.

Dicho esto, muchos usos de classson realmente tipos de registros / productos: existen para agrupar simplemente un grupo de elementos de datos. No es casualidad que el .sigilo no solo genere un accesorio, sino también:

  • Opta que el atributo se establezca mediante la lógica de inicialización de objeto predeterminada (es decir, class Point { has $.x; has $.y; }se puede instanciar como Point.new(x => 1, y => 2)), y también lo representa en el .rakumétodo de volcado.
  • Opta por el atributo en el .Captureobjeto predeterminado , lo que significa que podemos usarlo en la desestructuración (p sub translated(Point (:$x, :$y)) { ... }. Ej .).

¿Cuáles son las cosas que desearía si estuviera escribiendo en un estilo más procesal o funcional y utilizando class como un medio para definir un tipo de registro?

El diseño de Raku no está optimizado para hacer cosas inteligentes en setters, porque eso se considera algo pobre para optimizar. Está más allá de lo que se necesita para un tipo de registro; en algunos idiomas podríamos argumentar que queremos validar lo que se está asignando, pero en Raku podemos recurrir a los subsettipos para eso. Al mismo tiempo, si realmente estamos haciendo un diseño OO, entonces queremos una API de comportamientos significativos que oculte el modelo de estado, en lugar de pensar en términos de captadores / establecedores, que tienden a provocar una falla en la colocación. datos y comportamiento, que de todos modos es el punto de hacer OO.

Jonathan Worthington
fuente
Buen punto para advertir a los Proxys (aunque lo sugerí ha). La única vez que los he encontrado terriblemente útiles es para mi LanguageTag. Internamente, la $tag.regiondevuelve un objeto de tipo Region(como se encuentra almacenada internamente), pero la realidad es, que es infinitamente más conveniente que la gente diga $tag.region = "JP"sobre $tag.region.code = "JP". Y eso es realmente solo temporal hasta que pueda expresar una coerción Stren el tipo, por ejemplo, has Region(Str) $.region is rw(que requiere dos características planificadas pero de baja prioridad separadas)
user0721090601
Gracias @ Jonathan por tomarse el tiempo para elaborar la lógica del diseño: había sospechado algún tipo de aspecto de aceite y agua y para un organismo que no es CS como yo, realmente obtengo la distinción entre OO adecuado con estado oculto y la aplicación de clases como Una forma más amigable de construir titulares de datos esotéricos que tomarían un doctorado en C ++. Y al usuario072 ... por sus pensamientos sobre Proxy s. Sabía sobre Proxy s antes, pero sospechaba que ellos (y / o rasgos) son deliberadamente una sintaxis bastante onerosa para desalentar la mezcla de aceite y agua ...
p6steve
p6steve: Raku fue realmente diseñado para hacer que las cosas más comunes / fáciles sean súper fáciles. Cuando te desvías de los modelos comunes, siempre es posible (creo que has visto tres formas diferentes de hacer lo que quieres hasta ahora ... y definitivamente hay más), pero te hace trabajar un poco, solo lo suficiente para hacer Seguro que lo que estás haciendo es realmente lo que quieres. Pero a través de rasgos, proxies, slangs, etc., puedes hacerlo para que solo necesites unos pocos caracteres adicionales para habilitar realmente algunas cosas interesantes cuando lo necesites.
user0721090601
7

PERO - No entiendo por qué no puedo ir solo $ cv (43) Para establecer un atributo público

Bueno, eso depende realmente del arquitecto. Pero en serio, no, esa no es la forma estándar en que trabaja Raku.

Ahora, sería perfectamente posible crear un Attributerasgo en el espacio módulo, algo así is settable, que crearía un método de acceso alternativo que podría aceptar un único valor para ajustar el valor. El problema con hacer esto en el núcleo es que creo que hay básicamente 2 campos en el mundo sobre el valor de retorno de un mutador: ¿devolvería el nuevo valor o el antiguo? valor?

Contácteme si está interesado en implementar tal característica en el espacio del módulo.

Elizabeth Mattijsen
fuente
1
Gracias @Elizabeth - este es un ángulo interesante - Solo estoy respondiendo esta pregunta aquí y allá y no hay suficiente retribución en la construcción de un rasgo para hacer el truco (o habilidad de mi parte). Realmente estaba tratando de entender las mejores prácticas de codificación y alinearme con eso, y con la esperanza de que el diseño de Raku se alineara con las mejores prácticas, lo cual creo que es.
p6steve
6

Actualmente sospecho que te has confundido. 1 Antes de tocar eso, comencemos con lo que no te confunde:

Me gusta la inmediatez de la =tarea, pero necesito la facilidad de tapar las acciones secundarias que proporcionan los métodos múltiples. ... no entiendo por qué no puedo simplemente ir $c.v( 43 )a establecer un atributo público

Usted puede hacer todas estas cosas. Es decir, utiliza la =asignación y los métodos múltiples, y "simplemente vaya $c.v( 43 )", todo al mismo tiempo si desea:

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 posible fuente de confusión 1

Detrás de escena, has $.foo is rwgenera un atributo y un método único en la línea de:

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

Sin embargo, lo anterior no es del todo correcto. Dado el comportamiento que estamos viendo, autogenerado del compilador de fooalguna manera se está declarando método de tal manera que cualquier nuevo método con el mismo nombre en silencio sombras de TI. 2

Entonces, si desea uno o más métodos personalizados con el mismo nombre que un atributo, debe replicar manualmente el método generado automáticamente si desea conservar el comportamiento del que normalmente sería responsable.

Notas al pie

1 Vea la respuesta de jnthn para una descripción clara, exhaustiva y autorizada de la opinión de Raku sobre captadores / creadores privados y públicos y lo que hace detrás de escena cuando declara captadores / setters públicos (es decir, escribir has $.foo).

2 Si se declarara un método de acceso autogenerado para un atributo only, entonces supongo que Raku lanzaría una excepción si se declarara un método con el mismo nombre. Si se declarara multi, entonces no debería ser sombreado si el nuevo método también fue declarado multi, y debería lanzar una excepción si no. Por lo tanto, el descriptor de acceso autogenerado se declara con ni onlyni con, multisino que de alguna manera permite el sombreado silencioso.

raiph
fuente
Ajá - gracias @raiph - eso es lo que sentí que faltaba. Ahora tiene sentido. Según Jnthn, probablemente intentaré ser un mejor verdadero codificador de OO y mantener el estilo setter para contenedores de datos puros. ¡Pero es bueno saber que esto está en la caja de herramientas!
p6steve