Passing only some subroutine arguments by reference in Perl - perl

I'm writing a subroutine that takes a number of arguments. Most of those arguments are the standard pass-by-value sort, where changes made to them within the subroutine don't matter outside of it. But one of them is an object (blessed reference) that I'd like to make changes to that are available outside of the subroutine, if it's passed in. If it's not passed in, I would like to instantiate it and treat it the same way as if it were passed in (but returning it in the end).
For example:
my $foo = Private::Foo->new();
# $foo->{'something'} eq 'old value'
Private::Foo->do_things('abc', 'xyz', $foo);
# $foo->{'something'} eq 'new value'
my $foo2 = Private::Foo->do_things('def');
# $foo2->{'something'} eq 'old value'
package Private::Foo;
# ...
sub do_things {
my ($self, $arg1, $arg2, $foo) = #_;
unless (defined $foo) {
$foo = Private::Foo->new();
}
if ($arg1 eq 'abc') {
$foo->{'something'} = 'new value';
return;
}
return $foo;
}
I'd like to do this with as clean of syntax as possible, and I'm fine using any features available in Perl v5.22 and higher. (I've tried to figure out how to do this using refaliasing, but it isn't very clean.)
What am I missing?

First of all, subroutine arguments are always passed by reference.
$ perl -e'sub f { $_[0] = "def"; } my $x = "abc"; f($x); CORE::say $x;'
def
More importantly, your code does exactly what you asked already.
$ perl -e'
{
package Private::Foo;
sub new { my $class = shift; bless({ something => "old_value" }, $class) }
sub do_things {
my ($self, $arg1, $arg2, $foo) = #_;
unless (defined $foo) {
$foo = Private::Foo->new();
}
if ($arg1 eq "abc") {
$foo->{something} = "new value";
return;
}
return $foo;
}
}
use feature qw( say );
my $foo = Private::Foo->new();
Private::Foo->do_things("abc", "xyz", $foo);
say $foo->{something};
my $foo2 = Private::Foo->do_things("def");
say $foo2->{something};
'
new value
old_value
That said, you could clean up your method some:
sub do_things {
my ($class, $arg1, $arg2, $foo) = #_;
$foo //= $class->new();
if ($arg1 eq 'abc') {
$foo->{something} = 'new value';
}
return $foo;
}
It would be even better if you cleaned up your calling convention.
Private::Foo->do_something($arg1, $arg2, $foo);
my $foo2 = Private::Foo->do_something($arg1, $arg2);
makes far less sense than
$foo->do_something($arg1, $arg2);
( my $foo2 = Private::Foo->new )->do_something($arg1, $arg2);

Related

Can I reference a named subroutine with some arguments

I have a subroutine taking multiple arguments and want to make a reference to it with one of the arguments set, so that the reference takes one argument less. Optimal case would be
my $subref = \&routine($arg1);
...
my $result = $subref->($arg2,$arg3);
In perlref there is an example with an anonymous subroutine like this, however I cannot get the equivalent with a named one working.
Below is a full fledged example of what I mean. While $func (ref to anonymous sub) and $func2 (ref to named sub, but without arguments) work. $func3 gives the error "Not a CODE reference[...]".
Have I missed something or is this actually impossible?
use strict;
use warnings;
sub args{
my $arg1 = (shift or "none");
my $arg2 = (shift or "none");
my $arg3 = (shift or "none");
my (undef, undef, undef, $function) = caller(0);
return "me: $function\narg1 = $arg1\narg2 = $arg2\narg3 = $arg3\n";
}
sub just_a_ref {
return \&args;
}
sub new_arg_anon {
my $arg = shift;
return sub{
my $arg1 = $arg;
my $arg2 = (shift or "none");
my $arg3 = (shift or "none");
my (undef, undef, undef, $function) = caller(0);
return "me: $function\narg1 = $arg1\narg2 = $arg2\narg3 = $arg3\n";
}
}
sub new_arg {
my $arg = shift;
return \&args($arg);
}
my $func = new_arg_anon("one");
print $func->("two","three"); #works fine
my $func2 = just_a_ref();
print $func2->("un", "deux", "trois"); #works fine
my $func3 = new_arg("eins");
print $func3->("zwei", "drei"); #Not a CODE reference
You have to create a new anonymous function that does exactly that. Call the target function with one argument set and passing the rest of the arguments to it. In your example the new_arg function should be:
sub new_arg {
my $arg = shift;
return sub {args($arg, #_)};
}
\&args($arg) is \( args($arg) ), that is, a reference to the return value of the function call args($arg), not a reference to the function args called with the argument $arg.
print $func3; # SCALAR(0x8000a1a50)
To make a reference to a function that executes the args subroutine with $arg as the first argument, use
sub new_arg {
my $arg = shift;
return sub { args($arg,#_) };
}
(look at that, just like Georg Mavridis's answer)

Weakening captures using Sub::Quote

I'd like to weaken captured variables in the code generated by Sub::Quote. For example, here's the non-quoted alternative:
use 5.10.0;
use Scalar::Util qw[ weaken ];
{
my $s = 'foo';
my $x = sub { say $s };
weaken( my $y = $x );
my $bar = sub { &$y };
&$bar;
$x = undef;
&$bar
}
and the output:
foo
Can't use an undefined value as a subroutine reference [...]
And here's my Sub::Quote attempt:
use 5.10.0;
use Sub::Quote;
use Scalar::Util qw[ weaken ];
{
my $s = 'foo';
my $x = sub { say $s };
weaken( my $y = $x );
my $bar = quote_sub( '&$y', { '$y' => \$y } );
&$bar;
$x = undef;
&$bar;
}
and the output:
foo
foo
Obviously the captured $y isn't weakened. Is there a way of altering the generated code to weaken captured variables?
The documentation is sparse, and the Sub::Quote implementation is complex; I'm fairly convinced this isn't possible with the current code, but I'd love to be shown to be wrong.
my $bar = quote_sub( '&$y', { '$y' => \$y } );
is roughly the same as
my $bar = eval(q{ my $y = $y; sub { &$y } });
(It does more, but those bits are irrelevant to this question). As you can see, that creates a new strong reference to the sub[1].
As a workaround, you could add a layer of indirection:
my $bar = eval(q{ my $y_ref = \$y; sub { &{ $$y_ref } } });
This can be achieved by using:
my $bar = quote_sub( '&{$$y_ref}', { '$y_ref' => \\$y } );
There wouldn't be any problems if the $y created by Sub::Quote was an alias for your $y. This can be achieved using Data::Alias or an experimental feature introduced in 5.22.
This can be demonstrated using the following:
{
package Sub::Quote;
my $sub = sub {
my ($from, $captures, $indent) = #_;
join(
'',
"use feature qw( refaliasing );\n",
"no warnings qw( experimental::refaliasing );\n",
map {
/^([\#\%\$])/
or croak "capture key should start with \#, \% or \$: $_";
(' ' x $indent).qq{\\my ${_} = \\${1}{${from}->{${\quotify $_}}};\n};
} keys %$captures
)
};
no warnings qw( redefine );
*capture_unroll = $sub;
}
my $bar = quote_sub( '&$y', { '$y' => \$y } );
You could talk to the module's maintainer about adding an option that would cause the use of aliasing.
When you create a copy of a (strong or weak) reference, it's a strong reference.

Perl: pass implicit variable to custom sub

In Perl it is possible to implicitly pass the implicit variable to some built in functions, like this:
$_ = 'foo';
print; # prints foo
Is it possible to define such behavior for my sub? like this:
sub bar {
print $_[0];
}
$_ = 'foo';
&bar; # does not work
Thanks in advance.
$_[0] is first element of #_ array used to get values passed to subroutine. $_ is used as global implicit variable,
sub bar {
my ($arg) = (#_, $_);
print $arg;
}
local $_ = 'foo';
bar();
bar("explicit foo");
Single argument:
sub bar {
my $arg = #_ ? shift : $_;
...
}
Single argument (5.10+):
sub bar(_) {
my $arg = shift;
...
}
Multiple arguments:
sub bar {
my #args = #_ ? #_ : $_;
...
}
Multiple arguments (5.10+):
sub bar(_#) {
my #args = #_;
...
}

How can I do function partial application in Perl?

Is there any way to achieve partial application in Perl?
Suppose, I want to do something like:
sub each_file($arr, $op) {
$op->($_) for #{$arr};
...
}
sub each_line($op, $file) {
...
}
each_file($arr, each_line($op));
I want to partially apply each_line() to only $op, so it'll become a new function can be passed to $each_file, how do I express this in idiomatic Perl?
You can do this in Perl with two approaches combined:
A function which returns a function reference
Closures
Example:
sub each_file {
my ($arr, $line_fn) = #_;
$line_fn->($_) for #{$arr};
...
}
sub each_line {
my ($op, $file) = #_;
...
}
sub make_line_processor {
my ( $op ) = #_;
# This is closed over $op, which effectively becomes
# a constant for the returned function
my $fn = sub {
return each_line( $op, #_ );
};
return $fn;
}
# To call it:
each_file( $arr, make_line_processor($op) );
This can be an even more useful technique in cases where you don't want $op directly, but some expensive-to-fetch derivation of it. In which case you would calculate the derived value just once (in the make_line_processor function) and close over that instead.
# given some $op as implied by your code snippet
each_file($arr, sub { each_line($op, shift) });
# shift op will be applied when anonymous sub { … } is called
(Your code snippet doesn't make it entirely clear what you intend $op to be when you make the call to each_line. It's usually better to present small working programs.)
You can roll this functionality up into a class. Then you can overload the subroutine dereference operator to make it look like your class is really a code reference.
package Partial;
use overload '&{}' => \&call;
sub new {
my $class = shift;
my $code = shift;
bless {code => $code, args => \#_}, $class;
}
sub call {
my ($self) = #_;
return sub{ $self->{code}->(#{$self->{args}}, #_) }
}
You can then use it like this:
sub printArgs {
print join ", ", #_;
print "\n";
}
my $partial = Partial->new(\&printArgs, 'foo', 'bar');
$partial->('baz', 'bat');
# prints foo, bar, baz, bat

Unless constructor argument passed is a hash type, croak on invalid arguments?

I am vaguely confused a bit on different methods of passing certain arguments to the constructor type. I want to only pass a hash reference \%hash, or a list foo => 1, bar => 1 but not both and croak if anything else is passed i.e ( single elements, array reference ).
For example, I pass my reference or list.. (This works for the way I do this)
my $obj = foo->new;
my $data = $obj->dump( \%hash );
my $data = $obj->dump( foo => 1, bar => 1 );
or
my $obj = foo->dump( \%hash );
my $obj = foo->dump( foo => 1, bar => 1 );
Package module:
package foo;
use strict;
use Carp;
use Scalar::Util qw/reftype/;
sub new { return bless {}, shift }
sub dump {
my $class = shift;
my $self = shift;
unless ( reftype( $self ) eq reftype {} ) {
croak("Constructor method not a hash type!");
}
}
1;
I've also thought about using the conditional operator ? : here, but I can't get it to error properly.
my $self = reftype($_[0]) eq reftype {} ? shift : {#_};
Is there a better preferred way to do this?
We can look at the various ways your dump method can be called.
If we pass a "hash list", the number of elements is even (#_ % 2 == 0). Also, if at least one key-value pair is present, the first argument (a key) is a string, so not defined reftype $_[0] holds.
If we pass a hash reference, then the argument list should only hold this reference, and no other values: #_ == 1. The first argument will be a hash: reftype($_[0]) eq 'HASH'.
So to put the arguments in a hash reference, one could do something like:
sub dump {
my $invocant = shift;
my $hashref;
if (#_ == 1 and reftype $_[0] eq 'HASH') {
$hashref = $_[0];
} elsif (#_ % 2 == 0 and (#_ == 0 or not defined reftype $_[0])) {
$hashref = +{ #_ };
} else {
croak "Unknown argument format: either pass a hashref, or an even-valued list";
}
...; # do something with $hashref
}
To find out if the $invocant is the class name or an object, just ask it if it is blessed:
if (defined Scalar::Util::blessed $invocant) {
say "Yep, it is an object";
} else {
say "Nope, it is a package name";
}
There's no such thing as a "hash list". foo => 1, bar => 1, is just a four element list. Sounds like you want to accept hash refs and even numbers of args.
sub dump {
my $self = shift;
my %args;
if (#_ == 1) {
croak("...") if (ref($_[0]) // '') ne 'HASH';
%args = %{ $_[0] };
} else {
croak("...") if #_ % 2 != 0;
%args = #_;
}
...
}