AccessorFacade
NAME
AccessorFacade - turn indivdual get/set subroutines into a single read/write object attribute.
SYNOPSIS
use AccessorFacade;
use NativeCall;
class Shout is repr('CPointer') {
sub shout_set_host(Shout, Str) returns int32 is native('libshout') { * }
sub shout_get_host(Shout) returns Str is native('libshout') { * }
method host() is rw is attribute-facade(&shout_set_host, &shout_get_host) { }
...
}
DESCRIPTION
This module was initially designed to reduce the boiler plate code in a native library binding that became something like:
class Shout is repr('CPointer') {
sub shout_set_host(Shout, Str) returns int32 is native('libshout') { * }
sub shout_get_host(Shout) returns Str is native('libshout') { * }
method host() is rw {
Proxy.new(
FETCH => sub ($) {
shout_get_host(self);
},
STORE => sub ($, $host is copy ) {
explicitly-manage($host);
shout_set_host(self, $host);
}
);
}
...
}
That is the library API provides a sort of "object oriented" mechanism to set and get attributes on an opaque object instance that was returned by another "constructor" function. Because the object is an opaque CPointer it can only have subroutines and methods and not private data or attributes. The intent of the code is to provide fake "attributes" with rw methods (which is similar to how public rw attributes are provided.)
The above code will be reduced with the use of AccessorFacade to:
class Shout is repr('CPointer') {
sub shout_set_host(Shout, Str) returns int32 is native('libshout') { * }
sub shout_get_host(Shout) returns Str is native('libshout') { * }
method host() is rw is attribute-facade(&shout_get_host, &shout_set_host) { }
...
}
Named arguments are also supported so the above method could also be written as:
method host() is rw is attribute-facade(setter => &shout_set_host, getter => &shout_get_host) { }
(The call to explicitly manage is omitted for simplicity but how this is achieved is described in the documentation.) Libshout has a significant number of these get/set pairs so there is a reduction of typing, copy and paste and hopefully programmer error.
Whilst this was designed primarily to work with a fixed native API, it is possible that it could be used to provide an OO facade to a plain Raku procedural library. The only requirement that there is a getter subroutine that accepts an object as its first argument and returns the attribute value and a setter subroutine that accepts the object and the value to be set (it may return a value to indicate success - how this is handled is descibed in the documentation.)
TRAIT APPLICATION
The trait attribute-facade
should be applied to an object method
with no arguments that has the rw
trait, (if the method isn't rw then
assignment simply won't work, no check is currently performed.) The body
of the method should be empty, but will be discarded anyway if it isn't.
The arguments can be supplied as positional or named arguments.
The signatures of the trait can be thought of as being:
attribute-facade(Method:D: $method, &getter, &setter, &before?, &after?)
attribute-facade(Method:D: $method, :&getter!, :&setter!, :&before?, :&after?)
The individual arguments are:
&getter
Named parameter getter
.
This is the function that is called to retrieve the attribute value.
It has exactly one argument which is the invocant of the method
(i.e. self
). Its return value will be the value of the method
invocation.
&setter
Named parameter setter
.
This is the function that will be called to set the attribute value
(i.e. when it is assigned to.) It will be called with two arguments:
the invocant (self
) and the value to set. It may return a value
which will be passed to &after if it is defined.
&before
Named parameter before
.
If this is defined this will be called when the value is being set with the invocant and the value and its returned value will be passed to &setter instead of the original value, it is free to do what it likes as long as the resulting value is acceptable to the &setter.
This is how the explicitly-manage
would be applied in the example above:
sub managed($, Str $str is copy ) {
explicitly-manage($str);
$str;
}
method host() is rw is attribute-facade(&shout_get_host, &shout_set_host, &managed) { }
Or with named parameters:
method host() is rw is attribute-facade(getter => &shout_get_host, setter => &shout_set_host, before => &managed) { }
It is of course free to perform a validation and throw an exception or whatever may be appropriate.
&after
Named parameter after
.
This will be called after &setter with the invocant
and the return value of &setter
. It is primarily intended where
the setter may return a value to indicate the success or otherwise of
setting the attribute and this should be turned into an exception:
sub check($, Int $rc ) {
if $rc != OK {
die "value was not set";
}
}
method host() is rw is attribute-facade(&shout_get_host, &shout_set_host, Code, &check) { }
Note in the above example the Code
type if used as a placeholder
for the empty &before
(this is due to the way the "arguments" to the
trait are checked.)
This may be more conveniently written with named argument style:
method host() is rw is attribute-facade(getter => &shout_get_host, setter => &shout_set_host, after => &check) { }