読者です 読者をやめる 読者になる 読者になる

Perlで配列を1行ずつカンタンに出力したい

perl

Perlの小ネタシリーズ。

と言いつつ、文章はそこそこ長いです。

課題

さっそくですが、以下のような配列があるとして、

my @colors = qw/red blue orange green yellow/;

このまま出力すると、

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my @colors = qw/red blue orange green yellow/;

say @colors;

こんな感じになる。

redblueorangegreenyellow

これだとなんだかわからないので、要素間に改行を入れて、以下のように配列の中身を見やすくしたい、ということがよくある。

red
blue
orange
green
yellow

そのための方法として、初心者的に最初に思いつくのは、これとか。

for (@colors) {
    say $_;
}

あるいはこれとか。

say join "\n", @colors;

あるいは最初のやつを後置にするとか。

say $_ for (@colors);

で、これまではこういう場合、一番よく使うのは2番めの join を使う方法だったのだけど、昨日ふと思ったのが「いや、面倒すぎるだろ」ということで。

たしかに一回一回はさほどのタイプ数でもないのだけど、チリも積もればというやつで、これを一生繰り返し続けたら一体何タイプになるのだ、と。

というか、その join 方式にしても地味にダブルクオートやバックスラッシュなども入っていて、案外コストは高いとも言えるし。
今まで見てみぬふりをしてきたけど、これはちょっと改善しないとマズイよなあ……と。

で、普通こういうときというのはすでに誰かが同じことを考えていて、かつすでに解決策を講じてくれていて、さらにはすでにその方法を広く公開・共有してくれていたりするのだけど、寡聞にしてぼくはその辺のことをよく知らない。
皆さん、どうしてるんでしょうね? こういうの。

とりあえずなんとなーく、おぼろげな記憶というか印象として、あれ、こういうモジュールあったような……と思って、List::Util というのを当たってみたのだけど、
List::Util - search.cpan.org

ん〜、機能はむっちゃ充実しているのだけど、そして今あらためて見ながら「うわ、こんな機能あったんだ…… unique とか今度からはこれ使おう」などと思ったのだけど、当の求める機能はなさそう。(たぶん)

ちなみに、同モジュールについては以下に日本語での平易な解説があって、大変参考になりました。

検討

さて、ではあらためて、それならどうすればいいのか。

もしこれがターミナルから使うbashのコマンドとかだったら、これまでに結構似たようなことをやってきているので(ディレクトリ移動やファイルのリネームをするツールなど)、その応用でいけそうな気もするのだけど、今回はちょっと毛色が違うのでどうしたらいいのだか……。

それで、パッと思ったのはとりあえず自分で専用のモジュールを書いて、毎回それを use などで読み込んで使えるようにする、ということかなと。

もちろん、というか特定のモジュール化をしてしまうと、それを使ったコードは汎用性が低くなるというか、他の人とコードの共有をしづらくなってしまう可能性が高まるのだけど、ここでの目的はあくまで「配列を手軽に改行しながら表示したい!」という、個人的かつ一時的なデバッグ的な欲望にもとづくものなので、その限りにおいては「自分しか使わないモジュール」に依拠してしまってもとりあえず構わないかな、と。

ただし、それでも残る懸念としては、今回のモジュールは特定のファイル内でのみ使うものではなくて、コンピュータ内のどこでPerlを書いても使えるようにしたいという前提があるので、たぶん例の @INC 問題でハマる可能性がかなり高そう……

ということで、これまで幾度となく挑んでは敗れてきた @INC 系の情報に関してもあちこち調べて回った末、とりあえず至った結論は以下のようなものでした。
時系列で記してみます。

試行

とりあえずモジュール書く

まず、今回求めている機能は非常にシンプルなもので、こんな感じ。

sub foo {
    my @foo = @_;
    for (@foo) {
        say $_;
    }
}

んで、使うときには、

foo @colors;

とかする。だいぶシンプル!

名前を決める

さてそれで、そのとりあえず「foo」としているところ、つまりモジュール名、および関数名をどうするか……

命名の条件としては、そもそもタイプ数を少なくしたいので数文字で固めたい。
次に、内容をある程度想起させるもので。

今回の場合、配列に改行を挟みたいということなので、キャリッジリターンこと「cr」とか。あるいは「改行」で辞書を見ると「newline」とあるので「nl」とか?

ん〜、しかし「cr」は若干打ちづらいし、「nl」はそれよりは打ちやすいけど、見栄えがパッとしない……ああ、じゃあ「n」とか?

ということで、モジュールはこんな感じに。

n.pm
package n;
use strict;
use warnings;
use feature 'say';
use parent 'exporter';

our @export = qw(n);

sub n {
    my @n = @_;
    for (@n) {
        say $_;
    }
}

1;

はい。モジュール(パッケージ)名も関数名も「n」にしました。

初めてのエクスポート

上のコードで、自分的には初めてやったことがひとつあります。

use parent 'Exporter';
our @EXPORT = qw(n);

この2行を入れることによって、本来なら

n::n @colors;

としなければならないところが

n @colors;

というふうに関数呼び出しのみで済むようになっている。

この辺の情報は、平松さん( id:kaz_hiramatsu )の『雅なPerl入門 第3版』で学びました。(P120-121)
kazhiramatsu.hatenablog.com

Expoter モジュールや @EXPORT はいまだに全然理解できていないのだけど、やりたいことはできつつあるのでひとまずヨシ。

Minilla

では次。上のモジュールを書いただけだと、それを use しても読み込んでくれない&上記のとおり、今回は特定のディレクトリ内だけではなく、コンピュータ上のどこでPerlのコードを書いてもそのモジュールを使えるようにしたいので、たぶん通常のCPANモジュールをインストールするようなことをしなきゃいけない気がする。

しかし、どうやってそのモジュールを「インストール」すればいいのか……知らない。わからない。

ということで、これはけっこう調べる&理解するのに時間がかかったのだけど、最初に試したのは Minilla で。

この Minilla についても、上記の『雅なPerl入門』で詳しく解説されていてとても参考になりましたが、とりあえずこれを使って、

$ minil new n

で新規のレポジトリを作って、そこに上記の n.pm を入れて、同ディレクトリ内で

$ minil install

としたら……使えた!!

のですが、なんか違う……いや、ちゃんと動くのは動いたので、それで解決って言いたい気もするのだけど、第一にMinillaってそういうためのソフトウェアではなさそうだし、第二に(違和感としてはこっちのほうが重要だったのだけど)、今回やりたいことをやるためとしてはちょっと大げさすぎるような。

ということで、ひとまずやりたいことは実現できたものの、別の方法を探してもうしばらく旅を続ける……

環境変数 PERL5LIB を使う

それで訪れた次の試行。この章題は、以下の記事中からいただきました。
qiita.com

いつもお世話になっています @xtetsuji さんのQiita記事ですね。

そしてそれに近いネタとして、ほぼ同時点でたまたま検索して辿りついたのが以下。
perl-users.jp

じつはというか、前者のてつじさんの記事も、後者の記事にインスパイアされて&アップデート的な意味も含めて書かれたもののようで、だからというか相乗効果的にお二方の言わんとすることを理解しやすかったように思います。

それでその、肝心の目的の実現方法についてですが、どうやら任意のモジュールを読み込み可能な状態とする(@INC の中にそのモジュールを追加する)ためには、いくつかの選択肢があって、そのひとつとして、「PERL5LIB」という環境変数に、任意のパスを追加すればいいという。

正直なところ、ぼくはこのパス(PATH/環境変数)というやつもまともに理解できていなくて、しばらく前に読んだ良書『新しいLinuxの教科書』でようやくその一端に触れられた感じはあるのだけど、

新しいLinuxの教科書

新しいLinuxの教科書

とはいえ、まだあんまり腑に落ちてはいない。

いないのだけど、とりあえず bash_profile にこんなふうに記入したうえで、

export PERL5LIB=/path/to/n.pm

(実際には「/path/to/」の部分にはモジュールまでのディレクトリパスを書いている)

source コマンドでリロードすると、

source ~/.bash_profile

なんとモジュールを読み込むようになった。

以下、その模様。

f:id:note103:20160605004050g:plain

キタ!!!

オチ

さて、ここまでやってくる中で、初めはたんに「いちいち改行処理するのがメンドイ!」というだけの怠惰なモチベーションに突き動かされて旅に出てはみたものの、その過程で様々な思いがけない知見を得ることができまして、やはり学習楽しい! プログラミング楽しいな! と思うばかりだったのですが、その途上でひとつ気づいてしまったのが、上述の @xtetsuji さんの記事中にて、以下のような記述がありまして。

Debian wheezy のシステムPerl (最初からシステムに入っていたり、ディストリビューション公式のパッケージとして提供されているPerl) では以下のようなコマンドを打つと、以下のような出力が返ってきます

$ perl -E 'say for @INC'
/etc/perl
/usr/local/lib/perl/5.14.2
/usr/local/share/perl/5.14.2
/usr/lib/perl5
/usr/share/perl5
/usr/lib/perl/5.14
/usr/share/perl/5.14
/usr/local/lib/site_perl
.

ふむふむ……ん? このコード部分の1行目、

$ perl -E 'say for @INC'

これって、もしかすると……

my @colors = qw/red blue orange green yellow/;

say for @colors;  #<= 入れてみた

実行。

red
blue
orange
green
yellow

ああ〜。

for文の頭に、joinどころかスカラー変数すら置かずに「say」するだけでいいんだ〜……へえ〜……。
もしも最初にこれを知っていたら、ぼくたぶんこの試み一切やってなかったですね(笑)。

上のGIF動画を作りながらも思っていましたが、いくら関数名やモジュール名が1文字でラクだと言っても、いちいちファイルごとに use するのはやはり普通に手間だし。

まあ、とはいえ、「自作モジュールをひとまず自分だけで自由に使うにはどうすればいいか」という問題に対しては大きな前進を遂げることができたので、それでヨシとします……!

付録

元々は想定していなかったけど、当然のようにスカラー変数も出力できますね。

my $foo = 'bar';
n $foo;

f:id:note103:20160605093532g:plain

Cool!

って、自分だけが見る前提のプリントデバッグぐらいでしか使えませんが……。