その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

awesome-perlのご紹介およびメンテナの大募集

こんにちは。id:moznionと申します。Hachioji.pmというIT技術コミュニティに所属しています。

本記事はPerl Advent Calendar 2020の記事として記述されています。前日の記事は@mihyaeru21さんのGitHub Actions で Perl を動かすときのテンプレートでした。


Hachioji.pmという名前からわかるように、ここは元来はPerlを書く人が多かったコミュニティなのですが、時代の推移によりPerlを書く人は徐々に少なくなりつつあります。かく言う私自身も、かつてはPerlでそこそこ大規模なWebアプリケーションを書いて糊口を凌いでいましたが、ここ最近は仕事で (というかそこそこ規模の大きなコードを) Perlを書いたことは久しくありません。
Perl Advent Calendarなのになにを突然不敬なことを言い出すのかという感じですが、これは本筋に深く関係のある話のため記しました。


さて、Hachioji.pmではawesome-perlと呼ばれる、いわば「素晴しいCPANモジュール」のキュレーションプロジェクトのようなものを運用しています。元々は個人で運用されていたプロジェクトだったのですが、様々な事情があってHachioji.pmに運用が移管されたという経緯であったと認識しています。

github.com

awesome-goawesome-dockerなどといった、いわゆるawesome-*perl版だとお考えください。

awesome-perlはおかげさまで、多くの人に参照され活用されているようであります。モジュール追加等のpull-requestも定期的に送られてきておりありがたい限りです。


しかし、先に書いたように、Hachioji.pmではPerlを主戦場としているメンバーが少なくなってきており、awesome-perlのメンテナンスに一抹の不安を覚えるようになりました。
現状、メインでawesome-perlのメンテナンスを行っているのは私とid:uzullaさんの2名なのですが、私は先に述べたようにPerlからは一線を退いたに近い状況ですし、uzullaさんはPHPを主として活動されている方 (もちろんPerlは深く理解されています) となるため、両名とも現代のPerlのエキスパートとは言いがたい状況になっています。

その結果として、awesome-perlに送られてきたpull-requestについてはフォーマット等の議論・指摘はするものの、その内容についてはほぼ議論がバイパスされ、アッサリとマージされるようになってきているというのが近況となっています。廃墟にはしたくないのでpull-request自体のハンドルはされている、というような状況だとお考えください。
これについては質の観点での懸念があります。本来であれば提案されたモジュールの内容について議論をして精査し、然るのちにマージされるべきです。なんでもかんでも受け入れていては掃き溜めになることは避けられず、キュレーションとしては価値が失なわれてしまうためです。
しかし、何度か触れたようにPerlを現場で利用している人が少なくなってきているため、この健康的な手順を踏むことが徐々に難しくなってきています。


というわけで単刀直入に言いますと、awesome-perlを一緒にメンテナンスしてくれるメンバーを募集しています。現在も現役でPerlのプロジェクトを行われている方であれば非常にありがたいですが、そうでなくともメンテナンスを手伝ってくれる意思のある方であれば大歓迎です。

もし興味がある方がいましたら、私に直接連絡していただいても結構ですし、Hachioji.pmのSlackに来ていただいても歓迎です (普段は雑談をしているSlack Teamです)。


以上です。よろしくお願いします。awesome-perlでサクセスしませんか。

Email::MIME::ContentType が build_content_type と build_content_disposition を提供するようになっていた

Perl の話です.

metacpan.org

Email::MIME::ContentType 1.023 (なお本バージョンは TRIAL Release となっています) 以降から build_content_typebuild_content_disposition という関数が追加されています.それぞれ名前の通り Content-TypeContent-Disposition を構築する責務を担っています.

テストコードから一部拝借すると,

use Email::MIME::ContentType;

my $content_type = build_content_type({
    type => 'text',
    subtype => 'plain',
    attributes => {
        charset => 'us-ascii'
    }
}); # => 'text/plain; charset=us-ascii'

my $content_disposition = build_content_disposition({
    type => 'attachment',
    attributes => {
        filename => 'genome.jpeg',
        'modification-date' => 'Wed, 12 Feb 1997 16:29:51 -0500'
    }
}); # => 'attachment; filename=genome.jpeg; modification-date="Wed, 12 Feb 1997 16:29:51 -0500"'

というふうに使えるようになっています,ウオー便利ですね!


github.com
github.com

実に7年越しの feature request が結実した形になります *1.ありがとうございます!

*1:今となってはなぜこれが必要だったかを思い出せませんが……

aws-lambda-perl5-layer 書いた

[2018-12-05 追記]
ビルド済みのLayerを公開しましたので,そちらを使うと便利です.
See also: http://moznion.hatenablog.jp/entry/2018/12/05/211523
[追記ここまで]

先日のre:InventでCustom AWS Lambda Runtimesが発表され*1,これはつまり任意の言語でAWS Lambdaを実行することを可能とする機能なんですが,ということはPerl5が動くということでして,すなわち動くと嬉しいはず,したがって動かすためのLayerを書いたという話です.

github.com

この場合のLayerというのはPerl5を動かすための基盤だと思ってください.基本的にはbootstrapが実際にぐるぐる回ってperlの関数を実行するという感じになっております.
どのようにして動いているかは公式のTutorialが詳しいので参考されたい: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html


さて実際やってみた感想としてはbootstrapを書くのは非常に簡単で,ざっくり挙動を説明するとOver HTTPでLambdaのイベントを取ってきてその内容に従って関数を実行して,結果をPOST backするという非常に素朴な感じです.どんな言語でも比較的容易に書けるでしょう.

問題は実行基盤を整備するところで,これは scripts/build.sh に様々集約されているわけですが,エッセンスとしては

  • デフォルトだとPATHは /usr/local/bin:/usr/bin/:/bin:/opt/bin 以下に通っているのでそこにランタイムを突っ込む必要がある
  • Layerのzip archiveのルートは /opt に アタッチされるっぽい
    • なので bin/ やら lib/ やらをzipのルートに入れておけば良い感じになる
    • そこにperlのランタイムを突っ込むという作戦
    • bootstrapが必要とするライブラリ群もあらかじめここに入れておく
  • Layer zipのルートに bootstrap を置いておけばそのファイルがbootstrapとして認識される (つまり実行される)
    • 実行可能なpermissionが当たってないと動かない
  • ランタイムやツールのインストール (つまりzipping) する際には実環境と揃える必要がある
    • 適切なDockerコンテナを使って処理する必要がある
    • lambciの提供しているものを使った

という感じです.結構難しくてハマってしまいました……なお,実際に利用可能な環境変数群については以下を参考にされたい: https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html


まだドキュメントが十分に揃っていなく,また謎挙動もいろいろあるのですが *2 非常に面白い機能だと思いました.夢が広がりますね!!!

PerlのYAMLライブラリ性能比較

なんと2018年の記事です.皆様無事明けられておりますでしょうか.

さてYAML::XSには2017年に色々と変更が入り,実用するにあたり非常に便利な機能が色々と導入されました (具体的に言うと,$YAML::XS::LoadBlessed$YAML::XS::Booleanです).また安定化が図られました *1
というわけで個人的に,最近PerlYAMLをserialize/deserializeするにあたってはYAML::XSを使うことが多くなってきたわけですが,そこでふと各YAMLライブラリの性能について気になったのでベンチマークを取ってみたという次第です.以下はその記録です.

追記

とのことでしたので,YAML::PPについても記載しました.

ベンチマークコード
#!/usr/bin/env perl

use strict;
use warnings;
use utf8;
use Benchmark qw(cmpthese);
use YAML ();
use YAML::Tiny ();
use YAML::Syck ();
use YAML::XS ();
use YAML::PP::Loader;
use YAML::PP::Dumper;

my $yaml_text = do { local $/; <DATA> };

my $yaml_pp_loader = YAML::PP::Loader->new;

# Deserialize
cmpthese(10000, {
    'YAML' => sub {
        YAML::Load($yaml_text);
    },
    'YAML::Tiny' => sub {
        YAML::Tiny::Load($yaml_text);
    },
    'YAML::Syck' => sub {
        YAML::Syck::Load($yaml_text);
    },
    'YAML::XS' => sub {
        YAML::XS::Load($yaml_text);
    },
    'YAML::PP' => sub {
        $yaml_pp_loader->load_string($yaml_text);
    },
});

my $hashref = YAML::XS::Load($yaml_text);

my $yaml_pp_dumper = YAML::PP::Dumper->new;

# Serialize
cmpthese(10000, {
    'YAML' => sub {
        YAML::Dump($hashref);
    },
    'YAML::Tiny' => sub {
        YAML::Tiny::Dump($hashref);
    },
    'YAML::Syck' => sub {
        YAML::Syck::Dump($hashref);
    },
    'YAML::XS' => sub {
        YAML::XS::Dump($hashref);
    },
    'YAML::PP' => sub {
        $yaml_pp_dumper->dump_string($hashref);
    },
});

__DATA__
# From: https://github.com/kubernetes/kubernetes/blob/7bbab6234f99af9adb700ff30968794084b6ee12/examples/mysql-wordpress-pd/wordpress-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
        - image: wordpress:4.8.0-apache
          name: wordpress
          env:
            - name: WORDPRESS_DB_HOST
              value: wordpress-mysql
            - name: WORDPRESS_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-pass
                  key: password.txt
          ports:
            - containerPort: 80
              name: wordpress
          volumeMounts:
            - name: wordpress-persistent-storage
              mountPath: /var/www/html
          volumes:
            - name: wordpress-persistent-storage
              persistentVolumeClaim:
                claimName: wp-pv-claim
Deserialize結果
              Rate   YAML::PP       YAML YAML::Tiny YAML::Syck   YAML::XS
YAML::PP     251/s         --       -29%       -86%       -97%       -98%
YAML         351/s        40%         --       -80%       -96%       -97%
YAML::Tiny  1767/s       604%       403%         --       -81%       -87%
YAML::Syck  9524/s      3696%      2613%       439%         --       -30%
YAML::XS   13699/s      5360%      3803%       675%        44%         --
Serialize結果
              Rate       YAML   YAML::PP YAML::Tiny   YAML::XS YAML::Syck
YAML         588/s         --       -74%       -84%       -96%       -97%
YAML::PP    2299/s       291%         --       -38%       -84%       -87%
YAML::Tiny  3731/s       535%        62%         --       -74%       -79%
YAML::XS   14286/s      2330%       521%       283%         --       -19%
YAML::Syck 17544/s      2884%       663%       370%        23%         --

なるほどという結果です.
DeserializeはYAML::XSが最も速く,SerializeはYAML::Syckが高速という結果になりました.
もちろん対象とするYAMLのデータ構造に依存する結果ではありますが,性能を考えるのであればYAML::XSかYAML::Syckを使っておけば良さそうという感じですね.


それぞれ,

  • YAML: Pure Perl実装.これでしか解釈できない記法が(なぜか!)ある.
  • YAML::Tiny: Pure Perl実装.PPなのでシンプル.
  • YAML::Syck: libsyckのバインディング.XSで比較的高速.*2
  • YAML::XS: libyamlのバインディング.XSで比較的高速.libyamlという安心感がある.現代では安定している.
  • YAML::PP: Pure Perl実装.YAML 1.2をサポートするというモチベーションらしい.

という特徴があるので (そして各々のライブラリで解釈できる構文が異なる場合があり,これは業界ではヤムルの地獄と呼ばれています),状況に応じて使い分ける必要はありそうです.こちらからは以上です.



*1:昔はよく動かなくなっていた印象がある

*2:しかしlibsyckってどこで開発してるんですか?

JSON::XSでデシリアライズする時にtrueとfalseの扱いを変更する

Perl5の話題です.
JSON::XSを用いて,JSON StringをPerlのHashRefにデシリアライズする時にtrueとfalseの扱いを変えたいという話です.

デフォルト状態でJSON::XSを用いてデシリアライズすると, trueTypes::Serialiser::true すなわち JSON::PP::Boolean の真値として, falseTypes::Serialiser::false すなわち JSON::PP::Boolean の偽値として扱われます.
普通の処理であればこれで問題ないかもしれませんが,例えば「JSON StringをPerlのHashRefにしてそれを更に何らかのシリアライザに通す」といったような処理をしようとすると,JSON::PP::Boolean のオブジェクトだと取り回しが悪い場合があります.そういった時に,true/falseを任意の値にマッピングするにはどうすれば良いか.

https://metacpan.org/pod/JSON::XS#true,-false
Types::Serialiser - simple data types for common serialisation formats - metacpan.org

やり方としては上記のドキュメントのまわりにあるように, $Types::Serialiser::true 及び $Types::Serialiser::false を任意の値 (reference) に書き換えてやると良い.これらの変数はourで宣言されており,外から操作することができる.

use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
    $Types::Serialiser::true = \1;
    $Types::Serialiser::false = \0;
}

use JSON::XS qw/decode_json/;

my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);

例えばこのようにすると true\1 に,false\0マッピングされるようになります.

あるいは以下のように所望のオブジェクトにマッピングすることもできます;

package MyBool {
    sub new {
        my ($class, $val) = @_;

        return bless {
            val => $val,
        }, $class;
    }
}

use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
    $Types::Serialiser::true = MyBool->new(1);
    $Types::Serialiser::false = MyBool->new(0);
}

use JSON::XS qw/decode_json/;

my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);

注意としては, $Types::Serialiser::true 及び $Types::Serialiser::false はreferenceを要求しているという点です.仮にここにreferenceではないscalar値なんかを突っ込むとなにが起きるかというと

use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
    $Types::Serialiser::true = 1;
    $Types::Serialiser::false = 0;
}

use JSON::XS qw/decode_json/;

my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);
$ perl json.pl
Segmentation fault: 11

SEGVします. JSON::XSはxsの処理の中身で Types::Serialiser::trueTypes::Serialiser::false の中身を SV * つまりreference相当に割り当てているためです.ここで Types::Serialiser::true|false にscalar valueすなわち SV を割り当てると当然SEGVしてしまいます.

このへん
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L95
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L1974
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L136

<追記>
誤りの指摘を頂いたので修正しました.SvROKで事前チェックすれば良い気もするんですがどうなんでしょうね.

karupanerura [17:59]
リファレンスでもスカラ値でも `SV *` だから、ここでSvRVしているときにリファレンスじゃないとNULLが返ってくるのでSEGVしているというのが濃厚なきがする https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L139
[18:01]
リファレンスは `SvRV` というSVで、数値も `SvIV` というSVで差異はなくて、XSでSVを扱うときは基本的に `SV *` として扱うので、<追記ここまで>

きをつけましょう.

List::Haystack - A immutable list utility to find and count element


Perl5の話題です.List::Haystackというものを書きました.CPANにもアップロードしています.
経緯としては以下.


というものが欲しかったのです.List内の要素検索について,Immutableに処理をしたい + lazilyに処理したい,みたいなモチベーションです.

個別の使い方としてはpodを読んでもらうとして,例えば以下のように書くだけでシェークスピアのワードカウントなんてのを手っ取り早く書けるわけです.皆さん大学とかでやりましたでしょう.僕はやりませんでした.

use LWP::UserAgent ();
use List::Haystack;
use Data::Dumper;

my $response = LWP::UserAgent->new->get('https://ocw.mit.edu/ans7870/6/6.006/s08/lecturenotes/files/t8.shakespeare.txt');
my $txt = $response->decoded_content;
my $words = [grep { $_ } split /[^a-zA-Z0-9]/, $txt]; # XXX: Sloppy word separation!!!
my $haystack = List::Haystack->new($words);
print Dumper($haystack->haystack);

まあこういうのってperlだとHashとかで簡単に書けるんで別にモジュールにしなくても……と言う感じですが,アドホックに書くと同じような処理が随所に散らばってしまいがちで精神衛生上良くなかったので一丁モジュールにしたためた次第です.あとlazilyなconstructionが欲しかったので.

ご利用くださいませ.

Perl5のサブルーチン呼び出し時に二項演算子を期待していたら単項演算子の引数として認識されてしまう

sub func {
    return 100;
}

みたいなサブルーチンがあった時に,

say(func - 99);

みたいな感じで呼び出すと,パッと見 1 が出力されそうに見えるが,実際は 100 が出力される.
これは - 99func の引数として解釈されて食われるためである. B::Deparse するとわかりやすい.

$ perl -MO=Deparse func.pl
sub func {
    use warnings;
    use strict;
    use feature 'say';
    return 100;
}
use warnings;
use strict;
use feature 'say';
say func(-99);
func.pl syntax OK

ここから,二項演算子としての振る舞いを期待していた -99 と結合して単項演算子として扱われ, -99 が引数として扱われているのが読み取れる.

もちろん,サブルーチン呼び出し時にかっこを付けて

say(func() - 99);

という感じで呼び出してやれば当初の期待値である 1 が表示されるようになる.もしくはプロトタイプを利用する方法もある.

sub func () {
    return 100;
}

このように引数を取らないことをプロトタイプで明示すると,サブルーチン呼び出し時にかっこを付けなくても後続の値が引数として解釈されることはなくなる.その場合の B::Deparse の結果は以下のようになる.明解.

$ perl -MO=Deparse func.pl
sub func () {
    use warnings;
    use strict;
    use feature 'say';
    return 100;
}
use warnings;
use strict;
use feature 'say';
say func - 99;
func.pl syntax OK

これらは基本的な挙動ではあるのだけれど,日々を生きているとたまにハマりがち.例えば Time:PieceTime::Seconds をあわせて使ったときなんかに

use Time::Piece;
use Time::Seconds qw/ONE_HOUR/;
my $t = localtime - ONE_HOUR;
say $t;

などと書こうものなら,結果が Thu Jan 1 08:00:00 1970 というふうになり破滅する.localtime(-ONE_HOUR) となるからだ.
普段,perlの組み込み関数の時は (基本的に) かっこを付けずに記述するスタイルで書いていたので,組み込み関数である localtime をその感覚で使っていたところ綺麗にハマってしまった. *1


いままでは割と野生の勘みたいな感じでこのへんの諸問題を回避していて,「そういう掟なのじゃ」という感じで生活していたんですが真面目に B::Deparse とかで調べてみると色々学びがあるものですね.

*1:Time::Pieceはlocaltimeをoverrideするから最早組み込み関数ではないんでは? という意見はあると思いますが