時計を壊せ

駆け出してからそこそこ経ったWebプログラマーの雑記

WEB+DB Press Vol.119のPerl Hackers Hubに寄稿しました

WEB+DB PRESS vol.119の表紙
WEB+DB PRESS vol.119

Perl Hackers Hubも第64回ということでキリが良いですね。 個人的にはありがたいことに3度目のPerl Hackers Hub掲載です。

今回は「少しマニアックなPerlのテクニック」ということでPerlにまつわる少しニッチなTips集のようなものを書かせていただきました。

特殊変数を使って短くシンプルにコードを書き上げるテクニックであったり、Perl組み込み関数のsyscallを使って任意のシステムコールを呼び出す方法などを紹介します。 もっとPerlを使いこなしたい!と思っている方へのヒントとなるような内容を届けられたらと思っております。 もちろん、CPANモジュールを使わないことを推奨するわけではなく、あくまでもPerl本体の機能だけでもここまでのことができるぞという紹介になっております。

Dockerコンテナに潜ってDockerfileのデバッグをしたり、FaaSでprintデバッグをしたりすることも多い昨今ですが、まだまだオンプレミスの環境やその流れで構築されたIaaSの環境も多いことでしょう。 そのような環境でいざというときにログやデータを調査するための道具が少し足りない!というときに、CPANモジュールなどの外部パッケージのインストールが気軽にできる環境というのはそう多くはありません。

そのような環境で、外部パッケージに頼らず、多くの環境に最初からインストールされているPerlそのものの機能を使いこなすことができると、その際の解決策の選択肢の幅が広がります。 Perlそのものの機能も使いこなせれば、CPANモジュールなどの外部パッケージのインストールをする必要もなく、より素早く問題を解決できる場面もあることでしょう。 その際に役に立ったり、あるいはそのヒントになるようなものを提供出来たらと思っています。

そういうわけで、今回のテーマを書かせていただきました。 明日10/24に発売となりますので、もし良ければ書店等でお買い上げいただき、読んで頂けますと幸いです。

編集を担当して頂いた技術評論社の稲尾さんをはじめ、直接監修頂いた牧さん、ほか勤務先など各方面の関係者の方々にも様々な面で協力してもらい助けて頂きました。 この場を借りて改めて御礼申し上げます。ありがとうございました。

TypeScriptでタプル型の順列を得たい

たとえば、 [1, 2, 3] というタプル型があった場合に [1, 2, 3] | [1, 3, 2] | [2, 1, 3] | [2, 3, 1] | [3, 1, 2] | [3, 2, 1] みたいな組み合わせが欲しい。 これは順序を指定するようなケースに型制約を持たせるときに役立ち、io-tsなどを使ってType Guard関数を生成すれば外部入力に対しても型エラーが捕捉できる。

サッとググった感じ、自分のググりパワーが足りないのかうまいソリューションがみつからなかったので、あーでもないこーでもないとやって諦めてベタッと書いたところこんな感じになった。

type Permutations2<T extends readonly any[]> = [T[0], T[1]] | [T[1], T[0]];
type Permutations3<T extends readonly any[]> =
  | [T[0], ...Permutations2<[T[1], T[2]]>]
  | [T[1], ...Permutations2<[T[0], T[2]]>]
  | [T[2], ...Permutations2<[T[0], T[1]]>];
type Permutations4<T extends readonly any[]> =
  | [T[0], ...Permutations3<[T[1], T[2], T[3]]>]
  | [T[1], ...Permutations3<[T[0], T[2], T[3]]>]
  | [T[2], ...Permutations3<[T[0], T[1], T[3]]>]
  | [T[3], ...Permutations3<[T[0], T[1], T[2]]>];
type Permutations<T extends readonly any[]> = {
  2: Permutations2<T>;
  3: Permutations3<T>;
  4: Permutations4<T>;
}[T["length"] extends  2 | 3 | 4 ? T["length"] : never];

もっとうまくできそうな気がするが無限再起と判定されたり難しかった。n行ぶん記述すれば良いので雪だるま式に記述量が増えることもないが、添字を書き間違えたら変なタプル型が混じることになるのは微妙っぽい。 無限再起を回避する方法はboost-tsの実装とか参考にしたけど定数ぶんまでのパターンしかサポートしないということにするしかないのだろうか。

もっとうまく書けるぜ!ってひといたら教えて下さい。

これだった(教えてもらった):

susisu.hatenablog.com

やっぱ素直に再帰すると無限再帰扱いでエラーになるんだなぁ。 ベタに書くのも悪くないだろうか。

こういうのもあると教えてもらった:

github.com

シンプルでよさそうだけど無限再帰エラーにかからないのはobject型(っていうのかこれ?)をつかっているからだろうか?

Google App Engineでローカル開発をするときにdispatch.yamlをもとにReverse Proxyしてくれるツールを書いた

これです

github.com

なぜ作ったのか

dispatch.yamldispatch.xml はGoogle App Engine(以下GAE)のFrontendでルールベースでL7 HTTP Reverse Proxyしてくれるものです。

cloud.google.com

これはMicroservicesをやる上では大変便利なものになっています。
一方で、これをローカルで動かす手段が少なくとも自分の知る限りはありませんでした。

なので、いままでは各サービスを連携して動作検証したいときは、GAEにデプロイしてしまうか、
あるいは各サービスをそれぞれ別々のポートで立ち上げながら、各サービスに何らかの方法でそれらのマッピング情報を入れてサービス間で連携が可能なようにするなど工夫をする必要がありました。

別々のポートで立ち上げるところまではよしとしても、めんどくさいのでその後の各サービスには統一的にアクセスしたいわけです。
しかし、dispatch.yamldispatch.xml をnginx.confなどに移植してnginxを立ち上げたりなんてのもまためんどくさい。ぐぬぬ。

せめて dispatch.yamldispatch.xml をそのまま読んでよしなにReverse Proxyしてくれるサーバーがあると楽そうです。 ということで作りました。

使い方

README.mdにあるとおりですが、こういう感じで使えます。

$ (cd default; dev_appserver.py --port=8081 | tee -a dev.log) &
$ (cd mobile-backend; dev_appserver.py --port=8082 | tee -a dev.log) &
$ (cd static-backend; dev_appserver.py --port=8083 | tee -a dev.log) &
$ gae-dispatcher-emulator -c dispatch.yaml -s default:localhost:8081 -s mobile-frontend:localhost:8082 -s static-backend:localhost:8083

-c で渡した dispatch.yaml-s で渡しているサービスの待受情報をもとによしなにやってくれるというわけです。

これでデフォルトの localhost:3000gae-dispatcher-emulator

実際はforemanなどと組み合わせて使うのがおすすめです。

インストール

go製なので go get でいけます。

$ go get -u github.com/karupanerura/gae-dispatcher-emulator/...

また、Github Releasesにてバイナリの配布もしてますのでそちらもご利用いただけます。

Releases · karupanerura/gae-dispatcher-emulator · GitHub

macOSであればこういう感じでインストールできるでしょう:

$ curl -sfL -o ~/bin/gae-dispatcher-emulator https://github.com/karupanerura/gae-dispatcher-emulator/releases/download/v0.3.0/gae-dispatcher-emulator_darwin_amd64
$ chmod +x ~/bin/gae-dispatcher-emulator

実装

dispatch.yaml などを読めることを除けば素直なReverse Proxyとして動くと思います。

具体的には、X-Forwarded-For ヘッダを処理したりRFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1をもとにhop-by-hopなものとして定義されているヘッダフィールドを取り除くなどのことをしています。
このあたりはlib/Plack/App/Proxy.pm - metacpan.org の実装を参考にしました。

一応APIも公開していますのでforkして追いかけるのはつらそうだけど表面だけ変えたいとかあればお使いください(ただしインターフェースは突然変わるかもしれません):

godoc.org

なにか気になること、困ったこと、要望などあればお気軽にissueやTwitterなどでお知らせください。

errnoから何のエラーか簡単に調べるためのワンライナーの解説

qiita.com

こんなワンライナーがあります:

% perl -E 'say$!=24'
Too many open files

これの正体に迫っていきたいと思います。

どのように解釈されるのか

まずはこれがいったいどのようにParseされるのかを知るために、 -MO=Deparse を付けて実行してみましょう。

% perl -MO=Deparse -E 'say$!=24'
use feature 'current_sub', 'evalbytes', 'fc', 'say', 'state', 'switch', 'unicode_strings', 'unicode_eval';
say $! = 24;
-e syntax OK

say $! = 24 キモはこれです。 $! という変数に 24 を代入して、その式の結果を say が受け取っています。

Perlの代入式は左辺値の値が取られるため、これは以下のコードと等価です。( 参考: perlop - Perl の演算子と優先順位 - perldoc.jp

$! = 24;
say $!;

24 を代入したのに文字列が出てきた!?バグか!?と驚くかもしれません。 しかし、これは実はちゃんとPerlのドキュメントに明記されている挙動です。

特殊変数

Perlにおいては $! のようにシジル($ など)のあとの変数名が記号で始まるものは特殊変数と呼ばれ、処理系によって特殊な扱われ方をする変数になっています。

特定の特殊変数がどのようなものかは perldoc -v '$!' などとしたりすることで調べられます。 $! とはなんなのかを調べてみましょう。perldoc.jp でも簡単に調べられます。

Perlの組み込み変数 $! の翻訳 - perldoc.jp

$! はerrnoを扱うための特殊変数だということがわかります。

こんな記述があります:

参照されると、$! は C の errno 正数変数の現在の値を取得します。 $! に数値が代入されると、その値は errno に補完されます。 文字列として参照されると、$! は errno に対応するシステムエラー 文字列を返します。

つまり、そういう挙動をする特殊変数なので、そうなる。という感じです。

インターフェースに対する考察

でも、「文字列として参照されると」とは、どういうことなのか? なんで、こんな不自然に見えるインターフェースになっているのか?

これにはPerl特有のコンテキストという考え方が関わってきます。

コンテキスト

Perlは文脈的多態言語 (Contextually Polymorphic Language)として作られています。 これは要するに、データ表現を単一のスカラにまとめ、そしてそのスカラを評価した文脈によってデータ表現を変えることによって、同一の概念を1つの変数で簡便に扱うための概念です。

参考資料:

http://perldoc.jp/docs/perl/5.26.1/perldata.pod#Scalar32values https://www.slideshare.net/karupanerura/ss-98704540 https://www.slideshare.net/KondoYoshiyuki/yapc2012-20120929

このコンテキストを実現するための仕組みとして、Perlのスカラ変数は同一の値に対する複数の型のデータ表現を持つSV構造体として表現されます。 具体的にはPV/IV/NVという3つの値をSVに同時に持つことができます。

参考資料:

http://xsubtut.github.io/ https://metacpan.org/pod/B#OVERVIEW-OF-CLASSES

通常はこれらのIV/PVなどには同一のデータを単に型変換したデータが保持されます。

use Devel::Peek;

# IVだけを持つSVを作る
my $age = 17;
Dump $age;

# 文字列コンテキストで評価したことによってPV値が求められる
print "私は永遠の $age 歳です\n"; 

# $ageはIVとPVを持つSVに変わる
Dump $age;

実行してみるとこのとおりです:

SV = IV(0x7ffe9080dd88) at 0x7ffe9080dd98
  REFCNT = 1
  FLAGS = (PADMY,IOK,pIOK)
  IV = 17
私は永遠の 17 歳です
SV = PVIV(0x7ffe9080b820) at 0x7ffe9080dd98
  REFCNT = 1
  FLAGS = (PADMY,IOK,POK,pIOK,pPOK)
  IV = 17
  PV = 0x7ffe8f402de0 "17"\0
  CUR = 2
  LEN = 10

PV値に "17" という値が増えて、FLAGSPOK が増えています。(PVはPointer Valueなのでその名の通り、実際にはその文字列へのポインタです)

内部的にはこのようにして同一の値に対する異なる表現のデータを持ち、コンテキストでその評価を変えるのがPerlのContextually Polymorphic Languageたる世界観なのです。

ソフトウェア工学的に正しいかどうかはさておき、データを直感的に扱うためのアプローチとしてはユニークで面白い概念なのではないかと個人的には思います。

dualvar

さて、これを見た賢い人は「つまり単一のスカラに違うデータを入れることも可能なのでは?」と考えると思います。 まさにそのとおりで、標準モジュールのScalar::Utilにdualvarという機能があります。

https://metacpan.org/pod/Scalar::Util#dualvar

これを使うとこういったことができます:

use Devel::Peek;
use Scalar::Util qw/dualvar/;

# PVとIVを持つSVをdualvar関数で作る
my $age = dualvar 17, 'seventeen';
Dump $age;

# 文字列コンテキストで評価したことによってPV値が参照される
print "I'm $age years old.\n";

# 数値コンテキストで評価したことによってIV値が参照される
print "Next age: ", $age + 1, "\n";

結果としてはこうです:

SV = PVNV(0x7fa429802150) at 0x7fa42981da58
  REFCNT = 1
  FLAGS = (IOK,POK,IsCOW,pIOK,pPOK)
  IV = 17
  NV = 0
  PV = 0x7fa42b200570 "seventeen"\0
  CUR = 9
  LEN = 11
  COW_REFCNT = 1
I'm seventeen years old.
Next age: 18

IV/PVに別々の値が入っており、それがそれぞれのコンテキストに応じて使われている事がわかります。

即ち、Perlではerrnoとそのエラーメッセージは表裏一体の同一概念として扱えるべきだと考えて $! をdualvarのようなスタイルで実装していると考えられます。

ちなみに、一応書いておくと、dualvarを通常のソフトウェア開発で使うべき場面は基本的には存在しないと思います。

$!の正体に迫る

ここまでの知識を前提知識として知っていると $! はdualvarのようにして実装されているのでは?と想像できると思います。

実際に実行してみましょう:

use Devel::Peek;

$! = 24;
say $!;

Dump $!;

やはりIVにはerrnoが、PVにはそのエラーメッセージが格納されています。

Too many open files
SV = PVMG(0x7fb7ce827140) at 0x7fb7ce806ff8
  REFCNT = 1
  FLAGS = (GMG,SMG,NOK,POK,pNOK,pPOK)
  IV = 24
  NV = 24
  PV = 0x7fb7cdc07b00 "Too many open files"\0
  CUR = 19
  LEN = 21
  MAGIC = 0x7fb7cdc08ec0
    MG_VIRTUAL = &PL_vtbl_sv
    MG_TYPE = PERL_MAGIC_sv(\0)
    MG_OBJ = 0x7fb7ce807058
    MG_LEN = 1
    MG_PTR = 0x7fb7cdc028d0 "!"

では $!sayの文字列コンテキストで評価する前に参照してみるとどうでしょうか?

use Devel::Peek;

$! = 24;
Dump $!;

なんとPVは0です:

SV = PVMG(0x7fc936815b40) at 0x7fc93581d9f8
  REFCNT = 1
  FLAGS = (GMG,SMG,IOK,pIOK)
  IV = 24
  NV = 0
  PV = 0
  MAGIC = 0x7fc93570a450
    MG_VIRTUAL = &PL_vtbl_sv
    MG_TYPE = PERL_MAGIC_sv(\0)
    MG_OBJ = 0x7fc93581da58
    MG_LEN = 1
    MG_PTR = 0x7fc93570a480 "!"

$! を評価したときに何かが起きて、IVに応じたPVがセットされた。ということがわかります。 これを実行しているのがMAGICと呼ばれる仕組みです。

MAGIC

MAGICは変数の操作に対してフックするための仕組みです。 以下のようなことができます:

   関数ポインタ        その振る舞い
   ----------------    ------------
   svt_get             SV の値が取得された前に何かを行う。
   svt_set             SV に値を代入した後で何かを行う。
   svt_len             SV の長さを報告する。
   svt_clear           SV が表わしているものををクリアする。
   svt_free            SV に結び付けられてい領域を解放する。
   svt_copy            tie された変数のマジックを tie された要素にコピーする
   svt_dup             スレッドのクローン化中にマジック構造体を複製する
   svt_local           'local' 中にマジックをローカル変数にコピーする

出展: https://perldoc.jp/docs/perl/5.20.1/perlguts.pod#Magic32Variables

$!のMAGIC

Devel::Peekによると MG_TYPE = PERL_MAGIC_sv とあるので先のドキュメントと照らし合わせるとやはり特殊スカラ変数向けのMAGICが内部的に定義されていることが分かります。

 mg_type
 (old-style char and macro)   MGVTBL         Type of magic
 --------------------------   ------         -------------
 \0 PERL_MAGIC_sv             vtbl_sv        特殊スカラ変数

該当箇所のソースを追うと、 mg_vtable.hでこれが定義されており Perl_magic_setPerl_magic_get によって実際の処理が行われていることが分かります。

https://github.com/Perl/perl5/blob/v5.30.0/mg_vtable.h#L191

$! = 24 のように代入されるタイミングで Perl_magic_set が実行され、 MG_PTR = 0x7fc93570a480 "!" なので以下の分岐に入ってきます。

https://github.com/Perl/perl5/blob/v5.30.0/mg.c#L3074-L3089

代入元のスカラを数値コンテキストで評価したときと同じ値を、 SETERRNOマクロで一般的な errno.h の提供するグローバル変数のerrnoにもセットしています。VMSの場合は挙動が少し違うようです。

https://github.com/Perl/perl5/blob/v5.30.0/perl.h#L1330

そして $! が評価されるタイミングで Perl_magic_get が実行され、errnoをSVのIVスロットにセットし直して最終的にはstrerror関数によってerrnoのエラーメッセージを取得してPVスロットにセットしています。

https://github.com/Perl/perl5/blob/v5.30.0/mg.c#L1003-L1032 https://github.com/Perl/perl5/blob/v5.30.0/embed.h#L843 https://github.com/Perl/perl5/blob/v5.30.0/mg.c#L883-L897

こうして $! の挙動がどのように実現されていることが分かりました。

まとめ

$! はPerlの特殊変数でerrnoを扱います。その挙動はMAGICによって実装されており、dualvarのように振る舞います。 $! を使うときはコンテキストによって表現を変えるのでコンテキストを適切にコントロールしましょう。