Mailing List Archive

:writer return (was: Re: PEVANS Grant Report January 2024)
On 2024-02-12 17:45, Ovid wrote:
> [...]
> The only caveat is I realize the team never came to an agreement about
> what the :writer would return. Some insist upon the invocant (chaining
> mutators) while others want void. Returning a true value or previous
> value were minority opinions.
>

Some general remarks:

Chaining is mostly only good for configuration and setup contexts.

You generally start out with a copy of a default configuration, as an
object, and then specialize it.
Once specialized enough, you call something like $task->run, to put the
configuration to use, to solidify it, to finish it.

For returning a previous value, explicitly use a get_set method, that
itself explicitly calls get and then set, as then both get and set can
be overridden.

Any writer should return some (established or promised) final state in
some way, either inside the underlying (log-)object, or directly. Avoid
global variables.


-- Ruud


Random chaining example:

my $sql= SQL -> from(t1 => "X") -> join(t2 => "Y", -on => {"t2.[%
t1.name_lc %]_id" => "t1.id"}) -> where(...) -> select(...) ->
group_by(...) -> having(...) -> order_by(...);

my $rv= $sql -> prepare(...);

if ($rv->err) {
    ...;
}
else {
    $rv= $sql -> execute;
}
Re: :writer return [ In reply to ]
My own thoughts on what are good design...

For actual mutable objects such as these are, the writer methods should return
void, as they are statements and not expressions.

Were we talking about immutable objects such that a "setter" is actually a
shorthand for creating a new object with the same fields as the invocant but for
one field value being changed, then it should return the new object. But this
doesn't apply here.

Personally I'm a strong advocate for immutable objects where one sets all the
fields at initial construction time, and not using chained mutators.

I'm not going to argue strongly against a chained mutator design, but that's my
preference to not encourage it.

Another thing to consider though is if you want to be conservative then making
mutators void in the initial version is safest because you're providing the
simplest functionality one can rely on. You can choose to later have them
return the invocant to support chaining mutators, but you can't change chaining
to void later without breaking things.

-- Darren Duncan

On 2024-02-14 1:42 a.m., Ruud H.G. van Tol via perl5-porters wrote:
>
> On 2024-02-12 17:45, Ovid wrote:
>> [...]
>> The only caveat is I realize the team never came to an agreement about what
>> the :writer would return. Some insist upon the invocant (chaining mutators)
>> while others want void. Returning a true value or previous value were minority
>> opinions.
>>
>
> Some general remarks:
>
> Chaining is mostly only good for configuration and setup contexts.
>
> You generally start out with a copy of a default configuration, as an object,
> and then specialize it.
> Once specialized enough, you call something like $task->run, to put the
> configuration to use, to solidify it, to finish it.
>
> For returning a previous value, explicitly use a get_set method, that itself
> explicitly calls get and then set, as then both get and set can be overridden.
>
> Any writer should return some (established or promised) final state in some way,
> either inside the underlying (log-)object, or directly. Avoid global variables.
>
>
> -- Ruud
>
>
> Random chaining example:
>
> my $sql= SQL -> from(t1 => "X") -> join(t2 => "Y", -on => {"t2.[% t1.name_lc
> %]_id" => "t1.id"}) -> where(...) -> select(...) -> group_by(...) -> having(...)
> -> order_by(...);
>
> my $rv= $sql -> prepare(...);
>
> if ($rv->err) {
>     ...;
> }
> else {
>     $rv= $sql -> execute;
> }
>
Re: :writer return [ In reply to ]
Replying to myself...

Actually, if we have constructors with named arguments, then really what is the
benefit of having chained mutators, if people desire those as an object
initialization pattern?

So I say just make auto-generated :writer mutators void, developers should use
named constructor args instead, and in what hopefully are rare cases where one
might justify chained mutators, they could just declare those as regular methods
rather than auto-generated ones.

This combined with the conservative future-proofing I mentioned before, I'm now
more strongly on the side of :writer mutators should return void.

-- Darren Duncan

On 2024-02-15 8:09 p.m., Darren Duncan wrote:
> My own thoughts on what are good design...
>
> For actual mutable objects such as these are, the writer methods should return
> void, as they are statements and not expressions.
>
> Were we talking about immutable objects such that a "setter" is actually a
> shorthand for creating a new object with the same fields as the invocant but for
> one field value being changed, then it should return the new object.  But this
> doesn't apply here.
>
> Personally I'm a strong advocate for immutable objects where one sets all the
> fields at initial construction time, and not using chained mutators.
>
> I'm not going to argue strongly against a chained mutator design, but that's my
> preference to not encourage it.
>
> Another thing to consider though is if you want to be conservative then making
> mutators void in the initial version is safest because you're providing the
> simplest functionality one can rely on.  You can choose to later have them
> return the invocant to support chaining mutators, but you can't change chaining
> to void later without breaking things.
>
> -- Darren Duncan
>
> On 2024-02-14 1:42 a.m., Ruud H.G. van Tol via perl5-porters wrote:
>>
>> On 2024-02-12 17:45, Ovid wrote:
>>> [...]
>>> The only caveat is I realize the team never came to an agreement about what
>>> the :writer would return. Some insist upon the invocant (chaining mutators)
>>> while others want void. Returning a true value or previous value were
>>> minority opinions.
>>>
>>
>> Some general remarks:
>>
>> Chaining is mostly only good for configuration and setup contexts.
>>
>> You generally start out with a copy of a default configuration, as an object,
>> and then specialize it.
>> Once specialized enough, you call something like $task->run, to put the
>> configuration to use, to solidify it, to finish it.
>>
>> For returning a previous value, explicitly use a get_set method, that itself
>> explicitly calls get and then set, as then both get and set can be overridden.
>>
>> Any writer should return some (established or promised) final state in some
>> way, either inside the underlying (log-)object, or directly. Avoid global
>> variables.
>>
>>
>> -- Ruud
>>
>>
>> Random chaining example:
>>
>> my $sql= SQL -> from(t1 => "X") -> join(t2 => "Y", -on => {"t2.[% t1.name_lc
>> %]_id" => "t1.id"}) -> where(...) -> select(...) -> group_by(...) ->
>> having(...) -> order_by(...);
>>
>> my $rv= $sql -> prepare(...);
>>
>> if ($rv->err) {
>>      ...;
>> }
>> else {
>>      $rv= $sql -> execute;
>> }
>>
>
Re: :writer return [ In reply to ]
On Fri, Feb 16, 2024 at 5:43?AM Darren Duncan <darren@darrenduncan.net>
wrote:


> So I say just make auto-generated :writer mutators void, developers should
> use
> named constructor args instead, and in what hopefully are rare cases where
> one
> might justify chained mutators, they could just declare those as regular
> methods
> rather than auto-generated ones.
>
> This combined with the conservative future-proofing I mentioned before,
> I'm now
> more strongly on the side of :writer mutators should return void.
>

I agree. Corinna design follows the principle of parsimony
<https://github.com/Perl-Apollo/Corinna?tab=readme-ov-file#principle-of-parsimony>
(a
term borrowed from biology, but used slightly differently).

*Many things in the proposal are deliberately restrictive, such as Corinna
only allowing single inheritance. This is to allow Corinna to be cautious
in not promising too much. If we later find this too restrictive, we can
allow multiple inheritance. However, if we start with multiple inheritance
and discover we don't need or want multiple inheritance, we would break
existing code by taking it away.*


*Any proposals to change the RFC must consider the principle of parsimony.*


Thus, making mutators void is the safest approach because we can loosen
that restriction later without (as as I can tell) breaking existing code.
But if we allow the invocant to be returned and it's a mistake, we've
painted ourselves into a corner.

Best,
Ovid
Re: :writer return [ In reply to ]
On Fri, 16 Feb 2024 at 16:45, Ovid <curtis.poe@gmail.com> wrote:

> Thus, making mutators void is the safest approach because we can loosen
> that restriction later without (as as I can tell) breaking existing code.
> But if we allow the invocant to be returned and it's a mistake, we've
> painted ourselves into a corner.
>

If it currently returns an empty list, then `return $self->set_value($x),
$x` will break in future. The point of the "experimental" label is to be
able to try out decisions like this, with the option of improving things
later - being too restrictive isn't a good thing either.

If there's uncertainty about what it should do, just don't add the feature
to core at all: let it be handled by CPAN modules until a consensus emerges.

A mutator that doesn't return the invocant would be entirely useless to me,
for example, so if it's taking up prime ":writer" namespace for a feature
that's unusable, that'd be mildly annoying. I'd be much happier to `use
OO::FieldAttribute::ChainableWriter;` and have a custom `:writer` that does
the right thing instead.

>
Re: :writer return [ In reply to ]
On 2024-02-16 1:32 p.m., Tom Molesworth via perl5-porters wrote:
> On Fri, 16 Feb 2024 at 16:45 wrote:
> Thus, making mutators void is the safest approach because we can loosen that
> restriction later without (as as I can tell) breaking existing code. But if
> we allow the invocant to be returned and it's a mistake, we've painted
> ourselves into a corner.
>
> If it currently returns an empty list, then `return $self->set_value($x), $x`
> will break in future. The point of the "experimental" label is to be able to try
> out decisions like this, with the option of improving things later - being too
> restrictive isn't a good thing either.

The "void" talk is a colloquialism rather than being literal, it means in effect
that :writer routines end with "return;", and are explicitly not returning a
value at all. Meaning that it doesn't make sense to invoke them as an
expression, just as a statement, so no one should be writing "return
$self->set_value($x), $x" in the first place, so adding a return value would not
break anything. Why would one be writing that when it doesn't return a value?
-- Darren Duncan
Re: :writer return [ In reply to ]
On Fri, 16 Feb 2024 15:51:35 -0800
Darren Duncan <darren@darrenduncan.net> wrote:

> The "void" talk is a colloquialism rather than being literal, it
> means in effect that :writer routines end with "return;", and are
> explicitly not returning a value at all. Meaning that it doesn't
> make sense to invoke them as an expression, just as a statement, so
> no one should be writing "return $self->set_value($x), $x" in the
> first place, so adding a return value would not break anything. Why
> would one be writing that when it doesn't return a value? -- Darren
> Duncan

I've often wanted (as I've written to p5p@ many times) a `:void`
attribute on subs, to warn of such things. Imagine

sub a {}
sub b :void {}
sub c {}

my @x = ( a, b, c );

Pointless call of &main::b in non-void context at FILE line 5.


I just haven't got around to it yet.

--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: :writer return [ In reply to ]
On 2024-02-17 1:38 a.m., Paul "LeoNerd" Evans wrote:
> On Fri, 16 Feb 2024 15:51:35 -0800
> Darren Duncan <darren@darrenduncan.net> wrote:
>
>> The "void" talk is a colloquialism rather than being literal, it
>> means in effect that :writer routines end with "return;", and are
>> explicitly not returning a value at all. Meaning that it doesn't
>> make sense to invoke them as an expression, just as a statement, so
>> no one should be writing "return $self->set_value($x), $x" in the
>> first place, so adding a return value would not break anything. Why
>> would one be writing that when it doesn't return a value? -- Darren
>> Duncan
>
> I've often wanted (as I've written to p5p@ many times) a `:void`
> attribute on subs, to warn of such things. Imagine
>
> sub a {}
> sub b :void {}
> sub c {}
>
> my @x = ( a, b, c );
>
> Pointless call of &main::b in non-void context at FILE line 5.
>
> I just haven't got around to it yet.

Yes, that would indeed be nice to have, I would use it if it existed, and this
is then what :writer should use.

FWIW, from a general language design perspective, I prefer those languages that
make functions and procedures very structurally different routines in how they
are declared. So functions must return a value and be invoked within
expressions, and procedures must not return a value and must be invoked as
statements. Examples of languages like this include SQL and Pascal. In that
context, there is no "void" result type, instead you have a "procedure".

-- Darren Duncan
Re: :writer return [ In reply to ]
On Sat, Feb 17, 2024 at 10:51?AM Darren Duncan <darren@darrenduncan.net>
wrote:

> > I've often wanted (as I've written to p5p@ many times) a `:void`
> > attribute on subs, to warn of such things. Imagine
> >
> > sub a {}
> > sub b :void {}
> > sub c {}
> >
> > my @x = ( a, b, c );
> >
> > Pointless call of &main::b in non-void context at FILE line 5.
> Yes, that would indeed be nice to have, I would use it if it existed, and
> this
> is then what :writer should use.
>

Since we're on the subject of built-in attributes, I see we have the
following default ones now:

:lvalue
:method
:prototype(..)
:const
:shared

So we'd be adding :void to that list. Another one I would suggest would
make life much easier in many ways (at least for me) would be :apply for
creating Python-style decorators.

sub decorator1 ($func) {
say "first decorator";
return sub (@args) {
# maybe do something with args
return $func->(@args);
}
}

sub decorator2 ($func) {
say "second decorator";
return sub (@args) {
# maybe do something again with args
my $result = $func->(@args);
# maybe do something with $result
return $result;
}
}

sub my_func :apply(decorator1) :apply(decorator2) {
...
}

This would give us something close to Python decorators. See their PEP 318 (
https://peps.python.org/pep-0318/). This would remove many use cases for
before, around, and after modifiers (Moose and friends). We could also do
this:

sub novoid ($func) {
my $subname = get_subname($func);
if (not defined wantarray) {
croak("Calling '$subname' in void context is not allowed");
}
return $func;
}

sub fibonacci :apply(novoid) ($nth) { ... }

That could also be used to implement :void easily, and give us a more
generic extension mechanism for subs.

Best,
Ovid
Re: :writer return [ In reply to ]
Your generic proposal here sounds like it would be a good idea, though I don't
have time now to think about it further. -- Darren Duncan

On 2024-02-17 7:09 a.m., Ovid wrote:
> On Sat, Feb 17, 2024 at 10:51?AM Darren Duncan wrote:
>
> > I've often wanted (as I've written to p5p@ many times) a `:void`
> > attribute on subs, to warn of such things. Imagine
> >
> >    sub a {}
> >    sub b :void {}
> >    sub c {}
> >
> >    my @x = ( a, b, c );
> >
> >    Pointless call of &main::b in non-void context at FILE line 5.
> Yes, that would indeed be nice to have, I would use it if it existed, and this
> is then what :writer should use.
>
>
> Since we're on the subject of built-in attributes, I see we have the following
> default ones now:
>
>     :lvalue
>    :method
>     :prototype(..)
>     :const
>     :shared
>
> So we'd be adding :void to that list. Another one I would suggest would make
> life much easier in many ways (at least for me) would be :apply for creating
> Python-style decorators.
>
>     sub decorator1 ($func) {
>         say "first decorator";
>         return sub (@args) {
>            # maybe do something with args
>            return $func->(@args);
>         }
>     }
>
>     sub decorator2 ($func) {
>          say "second decorator";
>          return sub (@args) {
>              # maybe do something again with args
>              my $result = $func->(@args);
>              # maybe do something with $result
>              return $result;
>          }
>     }
>    sub my_func :apply(decorator1) :apply(decorator2) {
>         ...
>     }
>
> This would give us something close to Python decorators. See their PEP 318
> (https://peps.python.org/pep-0318/ <https://peps.python.org/pep-0318/>). This
> would remove many use cases for before, around, and after modifiers (Moose and
> friends). We could also do this:
>
>     sub novoid ($func) {
>         my $subname = get_subname($func);
>         if (not defined wantarray) {
>             croak("Calling '$subname' in void context is not allowed");
>         }
>         return $func;
>     }
>
>     sub fibonacci :apply(novoid) ($nth) { ... }
>
> That could also be used to implement :void easily, and give us a more generic
> extension mechanism for subs.
>
> Best,
> Ovid
Re: :writer return [ In reply to ]
Op 17-02-2024 om 16:09 schreef Ovid:
> So we'd be adding :void to that list. Another one I would suggest
> would make life much easier in many ways (at least for me) would be
> :apply for creating Python-style decorators.
>
>     sub decorator1 ($func) {
>         say "first decorator";
>         return sub (@args) {
>            # maybe do something with args
>            return $func->(@args);
>         }
>     }
>
>     sub decorator2 ($func) {
>          say "second decorator";
>          return sub (@args) {
>              # maybe do something again with args
>              my $result = $func->(@args);
>              # maybe do something with $result
>              return $result;
>          }
>     }
>    sub my_func :apply(decorator1) :apply(decorator2) {
>         ...
>     }
>
> This would give us something close to Python decorators. See their PEP
> 318 (https://peps.python.org/pep-0318/). This would remove many use
> cases for before, around, and after modifiers (Moose and friends). We
> could also do this:
>
>     sub novoid ($func) {
>         my $subname = get_subname($func);
>         if (not defined wantarray) {
>             croak("Calling '$subname' in void context is not allowed");
>         }
>         return $func;
>     }
>
>     sub fibonacci :apply(novoid) ($nth) { ... }
>
> That could also be used to implement :void easily, and give us a more
> generic extension mechanism for subs.


And a mechanism to easily translate :void into :apply(void) and this
would be even more awesome. Maybe something as simple as:

  use :void => :apply(void);


HTH,

M4
Re: :writer return [ In reply to ]
On Sat, 17 Feb 2024 16:09:31 +0100
Ovid <curtis.poe@gmail.com> wrote:

> This would give us something close to Python decorators.

You know those are doable already right now, yes?

E.g. use Attribute::Handlers, implement an `apply` attribute on CODE.
One of the arguments passed in to the handler for it is the globref for
the sub having the attribute applied to it. You can make a new sub and
assign it into the globref.

$ cat ovid-apply-attr.pl
use v5.36;

use Attribute::Handlers;

sub Wrap :ATTR(CODE)
{
my ( $pkg, $gv, $subref ) = @_;

warn "Applying 'wrap' attribute to $subref\n";

no warnings 'redefine';
*$gv = sub {
warn "Invoking wrapped sub with @_\n";
my $ret = $subref->( @_ );
warn "Returning $ret from wrapped sub\n";
return $ret;
};
}

sub add :Wrap ( $x, $y ) { return $x + $y }

my $ten = add 3, 7;
say "Ten is $ten";


$ perl ovid-apply-attr.pl
Applying 'wrap' attribute to CODE(0x55d1bf46eaf0)
Invoking wrapped sub with 3 7
Returning 10 from wrapped sub
Ten is 10


--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: :writer return [ In reply to ]
On 18.02.24 10:13, Paul "LeoNerd" Evans wrote:
> On Sat, 17 Feb 2024 16:09:31 +0100
> Ovid <curtis.poe@gmail.com> wrote:
>
>> This would give us something close to Python decorators.
>
> You know those are doable already right now, yes?
>
> E.g. use Attribute::Handlers, implement an `apply` attribute on CODE.
> One of the arguments passed in to the handler for it is the globref for
> the sub having the attribute applied to it. You can make a new sub and
> assign it into the globref.

What if it's an anonymous sub?
Re: :writer return [ In reply to ]
On Sun, 18 Feb 2024 10:19:59 +0100
Lukas Mai <lukasmai.403+p5p@gmail.com> wrote:

> > E.g. use Attribute::Handlers, implement an `apply` attribute on
> > CODE. One of the arguments passed in to the handler for it is the
> > globref for the sub having the attribute applied to it. You can
> > make a new sub and assign it into the globref.
>
> What if it's an anonymous sub?

Ah ;)

Even anonymous subs have GVs, but the way that attributes themselves
currently work means you don't get access to run any on the protosub,
only per closure that gets created.

That's part of a far wider topic of fixes that need looking into as
part of a bigger redesign of attributes in general.

--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/