- ベストアンサー
Perlの戻り値について
配列の戻り値が理解できていないのですが、 Perlで正しく動作させるためには どのように記述したら良いのでしょうか? 戻り値の動作を詳しく書いてある(出来れば図入りで) 本はありますか? 例) sub a { my @a = ('A'); return (@a, "B", 2); } my (@a, $b, $c) = &a; print "a=[@a] b=[$b] c=[$c]\n"; # a=[A] b=[B] c=[2] # これを期待していたが # a=[A B 2] b=[] c=[] # こちらになる
- みんなの回答 (6)
- 専門家の回答
質問者が選んだベストアンサー
#1 以下のようにしてご希望の結果が得られます。 #2 サブルーチン内外の @a は別物で、外の @a には戻り値のすべてが入ってしまいます。 #3 本ではないですが・・・過去の質問(リンク先)が参考になります。 # ------- 例1 ---------- use strict; sub a { my @a = ('A','C','D'); return (@a, "B", 2); } my @a = &a; my $c = pop(@a); my $b = pop(@a); print "a=[@a] b=[$b] c=[$c]\n"; # ------- 例2 ---------- use strict; sub a { my @a = ('A','C','D'); return (\@a, "B", 2); } my ($a, $b, $c)=&a; my $n=@{$a}-1; print "a=[@{$a}[0..$n]] b=[$b] c=[$c]\n";
その他の回答 (5)
- kapura
- ベストアンサー率50% (48/95)
すでにお気づきかもしれませんが、サブルーチンの戻り値として配列とスカラーの混合したものを返したい場合にどのようにすればいいのかという問題で注目すべきなのは、サブルーチンの書き方というより、むしろそのサブルーチンを使う部分 (代入文) です。 サブルーチンの戻り値を書くreturn文のところだとかに限らず、一般に配列を配列 (リスト) の要素にしたつもりで書いても、要素にしたつもりの配列は展開されて全体が連なった1つの配列と解釈されてしまうということを認識するのがポイントだと思います。これはPerlでは配列の要素に成り得るのが、スカラーだけということから来る帰結でしょう。つまり、サブルーチンの戻り値の書き方というところを難しく考える必要はなくて、return文などでいくら配列を含むリストを書いても、単純に1つの配列を返す場合と同じことになると、シンプルに考えればいいと思います。 質問で示されたサブルーチンaは配列@aを要素にもつ配列を返しているのではなくて、要素にしたつもりの配列がスカラーの並びに展開され、全体として1つの配列を返しているわけです。(@a, "B", 2)は(@aの要素の並び, "B", 2)ということです。結果として、そのサブルーチンを使用している my(@a, $b, $c) = &a; という代入文は my(@a, $b, $c) = ("A", "B", 2); としたことと同じことになるわけです (もちろんダブルクォートの代わりにシングルクォートでもいいです,より厳密に言えば右辺の&aはリストコンテキストで解釈されるために("A", "B", 2)となっていて、例えば左辺がmy $dとかだったら3という風にスカラーコンテキストで解釈されます)。 大胆に言えば、returnで返すものが(\@a, "B", 2)のようにリファレンスを使ったものでなく、(@a, "B", 2)を返そうとするコードを書いている時点で変だと感じるべきではないか思います。もちろん、1つの配列を返すのと同じことをしているのだと承知の上でそう書いているのであればいいのですが。また、my(@a, $b, $c) = &a; というような代入文を書いている時点でも変だと感じるべきだと思います。なぜなら、このように書いた場合、サブルーチンaの返すものがスカラーだろうが配列だろうが、$bと$cには何も代入されない (undef) わけで my @a = &a; my($b, $c); などと書いたのと同じだと認識しているのであればいいですが、勘違いの元になる書き方でしょう。 つまり、入門書の代入操作と行ったところを確認すればわかると思いますが、代入文を my(変数のコンマ区切り) = 配列 (あるいはスライスやリスト) という風に書くのであれば、No.3で説明されているように左辺でスカラー変数を先に書かないと変なわけです。必然的に、サブルーチンの戻り値の順序も変更する必要が出てくるわけです。どうしてもサブルーチンを変更したくないのであれば、代入文のところを変更して、popやスライスを使うとか工夫をする必要がでてくるわけです。リファレンスを使う方法もあるわけですが、その場合もNo.4で説明されているように実体を扱っていることを意識するというのはありますが、あまり難しく考えず単純に配列やハッシュなどをスカラー化した (それによって配列の要素にできるようになったりする) とまず考えればいいと思います。心配であれば、my @aa = @$a;のように@aの内容を@aaにコピーして、それ以降@aa扱うようにすれば、@aaを変更しても@aには影響しないので全く難しく考える必要はないと思います (@aのことは忘れて@aaを扱えばいい)。書籍としては、ULTIMATE PerlやEffective Perlなどが、取っ付きやすい問題集といった感じで勘違いを減らすのにいいと思います。 http://www.ascii.co.jp/books/detail/4-7561/4-7561-3057-7.html
- SE-1
- ベストアンサー率57% (26/45)
>リファレンスの場合は、厳密にサイズを取得する必要があるのでしょうか? my $n=@{$a}-1; print "a=[@{$a}[0..$n]] b=[$b] c=[$c]\n"; ↓ print "a=[@$a] b=[$b] c=[$c]\n"; 同じ結果になりました。あえてサイズを取得しなくてもいいみたいですね。
- BLUEPIXY
- ベストアンサー率50% (3003/5914)
>やはり、リファレンスが一般的なようですね。 はい、普通に、配列を関数の引数にすると、展開されてしまうので、 個別に配列として取り扱うのは、リファレンスにするのが普通です。 >リファレンスの注意しておかないといけない点は何かあるのでしょうか? 既にご理解されている通り、リファレンスの場合、実体をいじっているということを頭に入れておく必要があります。 >引数でリファレンスを使うと呼び出し元の内容が変わる危険があることは理解できましたが、戻り値の場合はどうなるのでしょうか? 同様だと思います。(サブルーチンで作られた実体をいじっているという意識が必要) C等と違って、Perlでは、参照がある限り、解放されるということがないので、どこで作られた値でも同じです。 >my @aa = @$a; >で後は普通に配列で使おうと考えているのですが、これでは何か不具合でもあるのでしょうか? @aaには、コピーが作成されるので、普通に配列として扱って問題ないです。 >以下のテクニックはスライスする?で厳密にサイズを取ってきていますが、リファレンスの場合は、厳密にサイズを取得する必要があるのでしょうか? 質問の意図するところがよくわかりませんが、 print "a=[@array]\n"; print "a=[@$ref]\n"; は、同じです。 @リファレンス で、デリファレンスしたものは、普通の配列と同様に扱えると思います。 勘違いコメントだったらすみません。
- BLUEPIXY
- ベストアンサー率50% (3003/5914)
my (@a, $b, $c) = &a; の時最初の配列が全てのデータを取り込んでしまいます。 並びを変更して return ("B", 2, @a); にして my ($b, $c, @a) = &a; にするのが簡単かと思います。
お礼
配列の戻り値が1つの場合は、popと同様に、 これを使うと簡単にできますね。 初めてしりました。 ありがとうございます。 やはり、リファレンスが一般的なようですね。 あまり、リファレンスを使っていないのですが、 リファレンスの注意しておかないといけない点は 何かあるのでしょうか? 引数でリファレンスを使うと呼び出し元の内容が変わる 危険があることは理解できましたが、戻り値の場合は どうなるのでしょうか?
- SE-1
- ベストアンサー率57% (26/45)
お気づきとは思いますが、a が違う意味(サブルーチン、配列、リファレンス)で3箇所使われていますので、実際には違う名前にしてくださいね。
お礼
なるほど、リファレンスを使うことが普通ということですね。 ありがとうございます。 私は、 my ($a, $b, $c)=&test; my @aa = @$a; my @bb = @$b; で後は普通に配列で使おうと考えているのですが、 これでは何か不具合でもあるのでしょうか? 以下のテクニックはスライスする?で厳密にサイズを 取ってきていますが、リファレンスの場合は、 厳密にサイズを取得する必要があるのでしょうか? my $n=@{$a}-1; print "a=[@{$a}[0..$n]] b=[$b] c=[$c]\n"; 理解不足で申し訳ございません。