Help us understand the problem. What is going on with this article?

perlでのコレクション操作 (前編)

Perl Advent Calendar 2019 の3日目ですよ。
昨日は papix さんの
Syntax::Keyword::TryとPerlのキーワードプラグイン (その1)」でした。


皆さんはPerlでコレクション操作をどうしてるでしょうか?
※ここでは、コレクションを「配列(リスト)っぽいデータ構造」全般を指す言葉として使っています。

コレクション操作の2つの方法

perl標準のリスト

何らかの元データを抽出や変換等の加工を施して処理結果を得たい場合、伝統的なperl標準の方法としては以下のようにリストに対してmap, grep等を適用させます。
※結合順序のために、連結した処理の順序が、記述の 逆順 になることに注意です。

perlの通常のリスト処理例
my @result =
    map  { ... }  # 変換処理3
    map  { ... }  # 変換処理2
    grep { ... }  # 抽出処理1
    @src;         # 元リスト

foreach (@result) {
    ...  # 逐次処理
}

コレクション モジュールを利用

一方、OOスタイルでやりたい場合は (何らかのライブラリを利用する必要がありますが)
以下のように処理の順に記述することができますね。

OOスタイルでのコレクション操作例
my $result = $src  # 元コレクション
    ->grep(...)    # 抽出処理1
    ->map(...)     # 変換処理2
    ->map(...);    # 変換処理3

$result->each(...);  # 逐次処理

何らかのフレームワークやライブラリを利用している場合など、OOスタイルで記述する場面が多いと思いますので、コレクション操作もOOでかけると処理順のままメソッドチェインで記述できて嬉しい。

    # 商品レポジトリを
    return $app->repos->items

        # 指定タグで検索
        ->find({tag => $req->{tag} })

        # ロジが障害情報に該当していたら除外
        ->grep(sub { !$trouble_info->{$_->deliverer_id} })

        # API用の加工を施す
        ->map(sub {
            +{
                id     => $_->id,
                name   => $_->name,
                url    => $_->url // sprintf('/items/%s', $_->id),
                enable => $_->stock && !$_->suspended,
            }
        })

        # jsonに変換
        ->to_json;

おすすめコレクション モジュール

コレクションのためのモジュールは沢山ありますが、カジュアルに使いやすくて候補になりそうなモジュールは ↓このあたりでしょうか。

OOスタイルを前提としたコレクションの実装方式に関して、オブジェクトがHASHベースかARRAYベースのどちらか?という大別ができますが、上記の2つはどちらもARRAYベースのオブジェクトとなっています。
デリファレンスしてそのまま配列として利用できるように実装されていますので、OOスタイルと伝統的スタイルを混在させる場合でも使いやすいと言えるかも。

Data::Perl::Collection::Array

push()やset()など、破壊的変更を施すメソッドも一通り一式用意されており、通常の配列でできることはほぼ同名のメソッドで代替操作が可能です。
Data::Perl はPerlの基本的データ型のためのベースクラスモジュールであり、これをベースにカスタマイズして利用する用途を想定しているようです。配列以外にもハッシュやその他の型用のクラスもあります。

Data::Perlの例
use Data::Perl qw(array);

# array()はコレクション作成のショートハンド
my $c = array(1,2,3);

# 値を10倍して、10より大きな値を絞り込み
my $c2 = $c->map(sub { $_ + 10 };
my $c3 = $c2->grep(sub { $_ > 10 });

# 破壊的操作
$c->push(100);

# 配列化
my @array = $c->all;

# デリファレンスして、通常のリストとして操作
say join ',', @$c;

Mojo::Collection

Webフレームワーク Mojolicious で利用されている、必要最小限の機能をもったコレクションモジュール。
Web以外ではあまり利用しないメソッドも含まれていますが、Mojoliciousプロジェクトならそのまま使えるし、シンプルな構造なのでそのまま継承して拡張して使っても良いですね。
なお、破壊的メソッドを用意しておらず、基本的にはイミュータブルなコレクションとして利用することを想定しているようです。

Mojo::Collectionの例
use Mojo::Collection qw(c);

# c()はコレクション作成のショートハンド
my $c = c(1,2,3);

# 値を10倍して、10より大きな値を絞り込み
my $c2 = $c->map(sub { $_ + 10 };
my $c3 = $c2->grep(sub { $_ > 10 });  

# 配列リファレンス化
my $array = $c->to_array;

# デリファレンスして、通常のリストとして操作
say join ',', @$c;

API比較

なお、どちらのモジュールも、その内部でList::MoreUtils, List::Utilなどのポピューラーなリスト操作用ライブラリを採用しているためか、同名のコレクションメソッドが多く見られます。
参考のため、イミュータブル系な操作に対する各コレクションのメソッドの対比を記述します。
特に太字で記述した箇所は注意が必要です。

(操作) (戻り値) perl標準 Data::Perl::Collection::Array Mojo::Collection
生成 コレクション (1, 2, 3) array(1, 2, 3) c(1, 2, 3)
写像 コレクション map map map
フィルタ コレクション grep grep grep
集約 (任意) - reduce reduce
ソート コレクション sort sort sort
each (なし) for / foreach - each ※戻り値=コレクション
最初にマッチ 要素 - first first
最初にマッチしたインデックス インデックス - first_index -
逆順 コレクション reverse reverse reverse
シャッフル コレクション - shuffle shuffle
平坦化 コレクション - flatten -
平坦化(再帰) コレクション - flatten_deep flatten
先頭から取得 コレクション - - head
末尾から取得 コレクション - - tail
最先頭要素 要素 - - first ※引数なし
最末尾要素 要素 - - last
長さ 要素数 scalar count size
連結 文字列 join join join ※戻り値=Mojo::ByteStream
空判定 bool - is_empty -
空要素削除 コレクション - - compact
重複削除 コレクション - uniq uniq
複製(shallow) コレクション my @b = @a shallow_clone c(@$c)
配列化 配列 (N/A) elements / all @$c
配列化(リファレンス) 配列リファレンス (N/A) - to_array

公開に時間がかかってしまってすみません。
もう少しお話したかったのですが、一旦公開します。
次はハッシュ型のコレクションや、拡張についてお話したいです。

明日は hkoba さんの
-nle と .pm から始める Perl 入門とかどうかしら?(前編)」です!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away