Re: Spotting an empty array as argument
by choroba (Cardinal) on Mar 25, 2021 at 20:14 UTC
|
To distinguish between no argument and an empty array, you can use the ;\@ prototype. It makes it impossible to specify more than one argument, though:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
sub mysay (;\@) {
my ($arr) = @_;
if (defined $arr) {
print "$_\n" for @$arr;
} else {
print "$_\n";
}
}
my @arr2 = qw( Two/1 Two/2 );
mysay(@arr2);
my @arr1 = qw( One );
mysay(@arr1);
$_ = "Don't show";
my @arr0;
mysay(@arr0);
$_ = "Underscore";
mysay();
# This doesn't compile.
# mysay(qw(a b c));
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] [select] |
|
|
| [reply] |
|
|
DB<40> sub my_say (;\@$$$) { dd \@_ }
DB<41> my_say @a
[["a1", "a2", "a3"]]
DB<42> my_say @a,$a
[["a1", "a2", "a3"], "A"]
DB<43> sub tst (@) { dd @_ }
DB<44> tst @a
("a1", "a2", "a3")
DB<45> tst @a,2
("a1", "a2", "a3", 2)
| [reply] [d/l] [select] |
|
|
| [reply] [d/l] |
|
|
Re: Spotting an empty array as argument
by GrandFather (Saint) on Mar 25, 2021 at 20:15 UTC
|
Perl "flattens" arrays and hashes passed as parameters to a sub into a list of scalar values (see the perlsub Description section, paragraph two. In your case it is not possible for the sub to tell the difference between a call with no parameters and a call containing one or more empty array and empty hash parameters.
Perhaps a solution is to use a "sayArray" sub that does the multi-line thing and use Perl's "say" in other cases? That has the advantage that it clearly signals to the user that something special is happening in the array case.
Perl's built in "say" doesn't need any magic for handling a "no argument" case. It just does what print does (in that case it prints nothing) then prints a newline, which is what it always does.
Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
| [reply] |
|
|
Right, I suspected there might not be any nice way of doing it. I mean, "say" does seem to do some magic in the "no argument" case, just like "print", since it treats it differently from the "empty array" case. But maybe that's just a special rule for built-in functions.
It's weird though, because there are so many built-in functions that have the same behaviour, defaulting to $_ if there are no arguments, and it's clearly useful, so it's surprising if there's no way of recreating that for your own functions.
| [reply] |
|
|
The prototype (_) can be used for the last argument. Some builtin functions can't be simulated, though, you can detect them by prototype('CORE::func') returning undef.
For example, you can easily replicate the behaviour of uc, as prototype('CORE::uc') returns _. Similarly, pack has the prototype of $_ (the underscore must be the last one). But print or chomp return undef, so their behaviour is more complex and prototypes can't express it. So there is a special rule, but only for some of the built-in functions.
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] [select] |
|
|
|
|
|
|
|
Re: Spotting an empty array as argument
by Discipulus (Canon) on Mar 25, 2021 at 20:07 UTC
|
Hello Chuma,
> how can I distinguish between the two cases – no argument, or an empty array?
I bet you cant, because in a sub you receive arguments into @_ and no arguments is/means an empty array.
Outside a sub it will be another story..
L*
There are no rules, there are no thumbs..
Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
| [reply] [d/l] [select] |
Re: Spotting an empty array as argument
by LanX (Saint) on Mar 25, 2021 at 23:39 UTC
|
> , but if given a list, it should print each item with a newline.
maybe changing $, is just good enough for your task?
DB<223> $,="\n"
DB<224> @a=(); @b=1..3; $_=666
DB<225> say @b
1
2
3
DB<226> say @a
DB<227> say
666
DB<228>
But remember to use local, before changing global variables. :)
| [reply] [d/l] |
|
|
Ha, there's certainly an easy solution! It doesn't address the more general problem, but for this particular purpose it does the job. Thanks!
The only thing slightly off here is that "saying" an empty list still makes a newline. You'd think getting n newlines for n outputs shouldn't be that hard, but anyway, close enough.
| [reply] |
Re: Spotting an empty array as argument
by LanX (Saint) on Mar 25, 2021 at 21:54 UTC
|
There is a conceptual hole in Perl's arguments parsing, and choroba is spot on with his explanation.
The only flexible workaround I found using prototypes is a (;&) , which will mean you need to put your parameters in a block.
Not sure if you are willing to pay that price of surrounding your parameters in curlies, but a block can hold any number and type of list elements.
DB<32> sub my_say (;&) { $_[0] ? say $_[0]->() : say $_ }
DB<33> @a=('a1'..'a3'); $a="A",$b="B"; $_=666
DB<34> my_say
666
DB<35> my_say {@a}
a1a2a3
DB<36> my_say {@a,$a}
a1a2a3A
DB<37> my_say {$a,1..3}
A123
DB<38>
| [reply] [d/l] [select] |
|
|
| [reply] |
Re: Spotting an empty array as argument
by ikegami (Patriarch) on Mar 26, 2021 at 19:23 UTC
|
You can't pass arrays (or hashes) to subs, only a sequence of zero or more scalars. There is no difference between
f(@a)
and
f($a[0], $a[1], $a[2], ...)
so you can't distinguish
my @empty;
f(@empty)
from
f()
At best, you can use prototypes to change what scalars are passed to a sub.
sub f(;\@)
will allow
f(@a) # Calls &f(\@a)
and
f() # Calls &f()
but not
f($x, $y)
What you want to achieve, however, can't be achieved using prototypes. You'd have to use something like Devel::CallParser.
Seeking work! You can reach me at ikegami@adaelis.com
| [reply] [d/l] [select] |
Re: Spotting an empty array as argument
by haukex (Archbishop) on Mar 26, 2021 at 22:15 UTC
|
Here's something that AFAICT meets your requirements, but since it's Friday evening I might be missing a test case for which it fails*. But as I mention here, it's really still just an approximation.
sub mysay (;+@) {
return say $_ unless @_;
if ( ref $_[0] eq 'ARRAY' ) { unshift @_, @{shift()} }
elsif ( ref $_[0] eq 'HASH' ) { unshift @_, %{shift()} }
say join "\n", @_;
}
* Update: Of course, just two minutes after posting I thought of one: say \@x would normally print ARRAY(0x...), this function dereferences it. Like I said, an approximation! I think you might be better off just biting the bullet and writing say join "\n", ...; ...
| [reply] [d/l] [select] |
|
|
DB<262> sub mysay (;+@) { print @_ }
DB<263> mysay 1,2,3
123
DB<264> mysay 1..3
DB<265> mysay 1..3,4..6
456
DB<268>
It's enforcing scalar context on the first argument, and this is one of the unfortunate cases where an operator is changing it's meaning on context. :(
| [reply] [d/l] |
|
|
mysay 1..3
Ah, a very good point, thanks!
Chuma: This once again underlines the fact that, unfortunately (!), you're probably not going to get everything you want here. Perhaps it would also make sense to take a step back and explain what you want to use this function for? And if you think about it again, do you really need to default to $_? Or could you split this into two different functions? etc.
| [reply] [d/l] [select] |
Re: Spotting an empty array as argument
by tobyink (Canon) on Mar 26, 2021 at 10:11 UTC
|
sub mysay (_) { print "$_\n" for @_ }
| [reply] [d/l] |
|
|
Try my test here.
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] |
|
|
| [reply] |
|
|
| [reply] [d/l] |
|
|
Re: Spotting an empty array as argument
by Chuma (Scribe) on Mar 26, 2021 at 13:43 UTC
|
Well, there is of course a way of doing it, but it's not pretty...
sub brutesay{
if(@_>=1){
for(@_){print("$_\n")}
}else{
($package,$filename,$line) = caller;
open $fh,$filename;
for $i(0..$line-2){<$fh>}
if(<$fh>=~/brutesay *(\(\))? *;/){print "$_\n";}
}
}
| [reply] [d/l] |
|
|
That'll break if you have two brutesays on one line ;-) print and say get special parsing by Perl, and as others have explained, you can't replicate their behavior 100% with Prototypes, everything will just be an approximation. I think the closest you'll get functionally is LanX's $, suggestion.
| [reply] [d/l] [select] |
|
|
Well yes, I was reluctant to suggesting parsing the source since it seemed far beyond your scope. (Caller has also some limitations if it comes to the line number)
IIRC is Carp listing the stack trace with the original arguments if called inside the debugger. That's because DB is caching all source lines internally.
You could do the same with a "passiv" source filter.
Since it would only read and not change the source it can't possibly cause any harm.
Another way is adding __DATA__ at the end of your file, because the DATA filehandle is just reading your source. ( seek and see ;)
Both possibilities need far less resources and are more reliable.
| [reply] [d/l] |
Re: Spotting an empty array as argument
by LanX (Saint) on Mar 26, 2021 at 17:47 UTC
|
Another idea:
Since one can change the output handle with select , it should also be possible to change the print method of the IO object.
In a perfect world are print and say only calling that method.
But who knows what kind of magic rules here and I'm too tired to check this out now. :)
update
See also tied file handles:
A class implementing a filehandle should have the following methods:
TIEHANDLE classname, LIST
READ this, scalar, length, offset
READLINE this
GETC this
WRITE this, scalar, length, offset
PRINT this, LIST <----
PRINTF this, format, LIST
BINMODE this
EOF this
FILENO this
SEEK this, position, whence
TELL this
OPEN this, mode, LIST
CLOSE this
DESTROY this
UNTIE this
| [reply] [d/l] |
|
|
| [reply] [d/l] |
|
|
use v5.12; # enable say & strict
use warnings;
{
package MySay;
use Data::Dump qw/pp dd/;
use Carp;
require Tie::Handle;
our @ISA = qw(Tie::Handle);
sub TIEHANDLE {
#carp pp '\@_: ', \@_;
bless \ my $i, shift
}
sub PRINT {
my $self = shift;
print STDOUT "$_" for @_
}
sub new {
my $self = shift;
open my $fh, ">&STDOUT";
tie *$fh, 'MySay';
return($fh);
}
# sub DESTROY {
# print STDOUT "DESTROY";
# # my $self = shift;
# select(STDOUT);
# }
}
{
package main;
# ---- test vars
$_='$_';
my @a = map { "\$a[$_]" } 0..2;
my @b;
{
select MySay->new();
say;
say @b;
say @a;
select(STDOUT)
}
say "AF","TER"
}
C:/Strawberry/perl/bin\perl.exe -w d:/tmp/pm/tie_handle.pl
$_
$a[0]
$a[1]
$a[2]
AFTER
with some effort it might even be possible to automatically destroy the selection at end of scope, such that one only needs to write MySay->select() once without any further need for visible select
But I got tired and wanted to get it done. :)
| [reply] [d/l] [select] |
|
|
Re: Spotting an empty array as argument (updated)
by LanX (Saint) on Mar 25, 2021 at 22:50 UTC
|
There might be a possibility tho, but it's a bit hackish ...
a mysay(@a) or mysay($a) will alias $_[0] to $a_[0] or $a resp. and so on
But $_[0] won't be aliased if there are no parameters.
So if there is an internal way to check this, you've won.
Unfortunately I'm to busy to check for this and I suppose you don't wanna dive into that amount of magic anyway ... :)
update
Sorry, never mind.
This won't help you distinguishing between no parameters and an empty @a.
| [reply] [d/l] [select] |
| A reply falls below the community's threshold of quality. You may see it by logging in. |