こらもう絶対バグだと思った挙動が仕様だったので共有します。
問題のコード
use Moose::Role;
has zero => ( is => 'ro', isa => 'Num', default => 0 );
has num => ( is => 'ro', isa => 'ArrayRef[Num]', default =>sub {[1..9]} );
requires 'list';
という前提で、このロールを継承したモジュールの中で、
sub list {
my $self = shift;
( $self->zero(), @{ $self->num() } );
}
と
sub list {
my $self = shift;
my @list = ( $self->zero(), @{ $self->num() } );
}
というように定義した場合、スカラコンテキストでの挙動に違いがあるという話です。
実は、リストコンテキストでは同じなのですが、前者をスカラコンテキストで評価すると@{ $self->num() }
の値のみ返します。
悲しいけどこれ仕様なのよね
Comma Operator
Binary "," is the comma operator. In scalar context it evaluates its left
argument, throws that value away, then evaluates its right argument and\
returns that value.
This is just like C's comma operator. In list context, it's just the list
argument separator, and inserts both its arguments into the list.
These arguments are also evaluated from left to right.
つまり( $self->zero(), @{ $self->num() } )
もしくはreturn ( $self->zero(), @{ $self->num() } )
などと明示的にリストをreturn
するように記載をしても、カンマで区切られた返り値は;
区切りに解釈され、スカラコンテキストでは
$self->zero();
@{ $self->num() };
の評価(要するに後者の値のみ)を返します。(リストコンテキストでは両方を連結したリストを返すので問題ありません)
調べてみてわかったのですが、これにはMooseは関係ありません。簡潔に記すと、
my $zero = 0;
my $num = [1..9];
sub list {
return( $zero, @$num );
}
my ($x) = list();
my $y = scalar list();
という定義の下で、perlの仕様で、$x
には0
(リストの最初の値)が、$y
には9
(@$numの要素数)が代入されるということです。10
にならないんです。直感に反しますよね?
ご指摘を受けたのでさらに掘り下げると、サブルーチンすら関係ありません。
my $zero = 0;
my $num = [1..9];
my ($x) = ( $zero, @$num );
my $y = scalar( $zero, @$num );
としても同様です。なんじゃこりゃ1
妥当な解決策
-
一度配列に代入する
my @return = $self->zero(), @{ $self->num() };
-
一度無名配列を作りデリファレンスを返す
@{ [ $self->zero(), @{ $self->num() } ] };
-
map
関数を使うmap {$_} $self->zero(), @{ $self->num() };
サブルーチン内では、return
はつけてもつけなくても一緒です。
sub{}
の中の最後の評価値を自動的にreturn
するのがPerlの仕様です。
個人的な意見
明示的に()付きで書き添えた場合には()内を一つのリストと見傚して欲しいです。
または
scalar( $zero, @$num )
のように、リストをCORE::scalar()
に与えたときに警告を発しても良いんじゃないかと。2
SEE ALSO
- 先ほど詳細をGitHubにまとめましたので追試等はこちらからどうぞ。
- Why do I get the last value in a list in scalar context in perl? - stackoverflow
- Comma-Operator in perlop