いい感じにuseするのを助けてくれるApp::perlimportsのご紹介

これはPerlアドベントカレンダー2021の12日目の記事です。

昨日はshogo82148さんで、Perl 5.35.5 の iterating over multiple values at a time を先取りでした。

今回はPerlでもいい感じのモジュールのImportをしてくれるApp::perlimportsの紹介です。

App::perlImports

このモジュールはThe Perl and Raku Conference 2021で作者本人からトークされていて、スライド及び動画が上がっています。

www.youtube.com

スライドはGitHub上にHTMLがあるので手で落とす必要があるようです...。(GitHubPagesは特に使っていない模様)

curl https://raw.githubusercontent.com/oalders/presentations/main/slides/6-perlimports/remark.html -o perlimports.html && open perlimports.html

「Where Did That Symbol Come From?」とトークタイトルが示す通り、Perlのシンボルがどこからimportされたかを明確にしたいというのが実装の動機になっているようです。合わせて動き自体はgolangでimportしているモジュールをいい感じに管理してくれるgoimportsを参考にしているようです。

このモジュールについて説明するにはまず、Perlのモジュールロードがどのように行われているかを確認する必要があります。ざっと見てみましょう。

Perlのモジュールロード

Perlはモジュールをロードする方法に主にuseを使った方法とrequireを使った方法があります。*1 歴史的経緯でrequireの方が先に出ていて、 Perl5でuseが導入されています。 requireはモジュールロード以外に外部ファイルの読み込みにも使われます。

pointoht.ti-da.net

近年はuseを使ってモジュールロードをするのが一般的です。

例えば、PerlにデフォルトでバンドルされているHTTPクライアントのHTTP::Tinyを使う場合は次のようにロードします。

use HTTP::Tiny;

my $ua = HTTP::Tiny->new();

ロードタイミング

Perl動的言語でありますが、useを使ったモジュールロードの処理は、該当のPerlコードをPerlインタプリタが読み込む、コンパイルタイムに行われます。 例えば存在しないHogeモジュールをロードしようとすると、次のようにprintが走ることはなくエラーで死にます。

#!/usr/bin/env perl

use strict;
use warnings;
print "hello";
use Hoge;
❯ perl hoge.pl
Can't locate Hoge.pm in @INC (you may need to install the Hoge module) (@INC contains: /Users/anatofuz/.plenv/versions/5.32.0/lib/perl5/site_perl/5.32.0/darwin-2level /Users/anatofuz/.plenv/versions/5.32.0/lib/perl5/site_perl/5.32.0 /Users/anatofuz/.plenv/versions/5.32.0/lib/perl5/5.32.0/darwin-2level /Users/anatofuz/.plenv/versions/5.32.0/lib/perl5/5.32.0) at hoge.pl line 2.
BEGIN failed--compilation aborted at hoge.pl line 2.

対して、各関数呼び出しなどの処理は実行時に解決されます。 そのためuseを忘れても、対象のメソッドを実行しない限りは処理が止まることはありません。 例えば次のコードは、HTTP::Tinyをuseしていませんが、呼び出す前にdie(Perlでのエラー終了)をして処理を止めるので、ロードエラーは発生しません。

#!/usr/bin/env perl
use strict;
use warnings;


print "hello\n";
die;

my $ua = HTTP::Tiny->new();
$ua->get('https://example.com');
perl hoge.pl
hello
Died at hoge.pl line 6.

useの処理

ではuseは何をしているのでしょうか。Perldoc.jpを見るとこう書かれています。

指定したモジュールから、現在のパッケージにさまざまな内容をインポートします; 多くは、パッケージのサブルーチン名や、変数名に別名を付けることで、 実現されています。 これは、以下は等価ですが

BEGIN { require Module; Module->import( LIST ); }

https://perldoc.jp/func/use

BEGIN { }はBEGIN ブロックと呼ばれるもので*2コンパイルタイムに実行させるという処理です。 中ではrequireを使い、モジュールを読み込んだ後に、そのモジュールにあるimportメソッドを実行しています。

つまり、前述したPerlのモジュールロードのuserequireを比較すると次のような違いがあることがわかります。

項目 use require
ロードタイミング コンパイルタイム 実行時
副作用 importを実行する 読み込みのみ

整理したところで、このimportとは一体何でしょうか。 一般的には、これはモジュールで定義しているメソッドを、呼び出し元のコードで定義したかのように使うために実行されるものです。 詳しく見てみましょう。

import

Perlでは特定のモジュールをuseすると使えるようになるメソッドがいくつか存在します。 例えばコアモジュールのJSONを扱うライブラリであるJSON::PPをuseすると、任意のPerlのハッシュをJSON文字列化できるencode_jsonが使えるようになります。

use strict;
use warnings;

use JSON::PP;

print encode_json({hoge => 'hello', isBool => \0});

encode_jsonJSON::PPで定義されているメソッドなので、通常はJSON::PP->encode_jsonのように使う必要があります。 importは特定のメソッドを、useしたタイミングで通常の関数のように呼び出せるようによしなにやってくれる処理になっています。

この処理のことをPerlでは関数のエクスポートなどと読んでいます。 近年ではExporterモジュールが行っています。 あるモジュールで、Exporterモジュールを継承、もしくはuseすると、そのモジュール内で自動的に関数のエクスポート処理をimport関数として登録してくれます。

あくまでExporterを使った場合なので、独自にimport関数を定義することも可能です。 その場合は通常の関数のように呼び出せるようによしなにやってくれる処理以外の処理をuse時に行うように差し込むことが可能となります。 例えばClass::Accessor::LiteはimportをExport以外の目的に使っており、オブジェクトのアクセサを生成する処理を行っています。

use Class::Accessor::Lite (
    new => 1,
    rw  => [ qw(foo bar) ],
    ro  => [ qw(baz) ],
    wo  => [ qw(hoge) ],
);

Perlでモジュールをuseする際に、exportしたい関数名の指定ではないような引数を渡しているモジュールは、ほぼ独自にimportを書いていると考えても良いでしょう。

use時に差し込まれるモノたち

Exporterを使っているモジュールがuseされたときに差し込むものは、モジュール側で定義されている@EXPORT配列に積んだシンボルになっています。((ここでのシンボルはPerlのメソッド(関数、サブルーチン)の他に、スカラ変数や配列も含みますが、Exporterを使う上ではメソッド以外のシンボルを使うと問題が発生しがちなので、基本はメソッドのことです ))

例えば、Hoge::Firstモジュールをuseしたときに、hogeメソッドを差し込みたい場合は、次の様に定義します。

package Hoge::First;
use strict;
use warnings;
use feature qw/say/;

use Exporter 'import';

our @EXPORT = (qw/hoge/); #サブルーチンの名前を文字列で配列の中にいれるとEXPORTされる

sub hoge {
  say 'this is first!';
}

1;

Perl@EXPORTに積まれているオブジェクトをすべてロードします。 このためuseしているモジュールが、内部で@EXPORTに何かしら詰めていた場合、予期せぬシンボルをロードする可能性があります。 また、あるPerlファイルの中で出てきているメソッドが、一体どのモジュールから提供されているか、はたまたデフォルトのメソッドであるかの判断が難しくなってしまいます。 使用しているメソッドが、どのモジュールから提供されているのが解りづらいのは、リファクタリングの結果すでにあるモジュールが提供しているメソッドを使わなくなったのに、useし続けてしまうモジュールが出たり、逆にプログラミングしている上で使えるだろうと思って書いたメソッドが、実はuseしないといけなかったなどの問題を誘発しがちです。

また、同じ名前のオブジェクトをロードしてしまうと、呼び出し順序によって使われる関数が異なってしまいます。 例えばHoge::FirstHoge::SecondモジュールでそれぞれhogeをEXPORTしてみます。

package Hoge::First;
use strict;
use warnings;
use feature qw/say/;

use Exporter 'import';

our @EXPORT = (qw/hoge/);

sub hoge {
  say 'this is first!';
}

1;
package Hoge::Second;
use strict;
use warnings;
use feature qw/say/;

use Exporter 'import';

our @EXPORT = (qw/hoge/);

sub hoge {
  say 'this is second!';
}

1;
#!/usr/bin/env perl
# main.pl
use strict;
use warnings;

use Hoge::First;
use Hoge::Second;

hoge();

実行すると、最後にロードされたSecondが出てきます。

❯ perl -Ilib hoge.pl
this is second!

@EXPORTはこのような思いがけない処理が走る可能性があるので、Exporterモジュールのコメントにも「とりあえずEXPORTするのはやめよう」といったことが書かれています。 pointoht.ti-da.net

さらにはPerlのLinterであるPerl::Criticにも@EXPORTを警告するルールが追加されています。

metacpan.org

とはいえ関数のエクスポートは便利なので、それはそれで使用したいです。 Perlでは@EXPORTの他に@EXPORT_OKがあります。これはデフォルトではすべてをロードせず、use時にエクスポートしたい関数名を書くことを強制させる機能です。

例えばこんな感じにuse時に関数名を指定します。

use JSON qw(encode_json);

実は@EXPORTで宣言している場合もuse時に関数名を指定している場合はそのメソッドしかエクスポートされません。関数名を何も指定せずに()を書いた場合は何もエクスポートされないため、宣言的に関数名を書くようにするとほぼ@EXPORT_OKと同様の振る舞いをします。

自分が書いているコードに出てくるメソッドの宣言元をはっきりさせる単純な解決方法はEXPORTをやめ、Hoge::Foo->bar()の様にフルパスでメソッドを実行することですが、テストヘルパなどEXPORTした方が利便性が高いものもあるでしょう。 decode_utf8など、EXPORTされたメソッドを使うのがイディオム的に認知されているものもあります。 エクスポートした関数を使うのと、どこから取り込んできたのかの視認性を高めるには、use時にエクスポートしたい関数名を漏れなく書くしか無いでしょう。 しかしこれを人間がやるのは面倒!!! ということで作られたのがperlimportsです。

....と、ここまで書いたところで奇跡的に裏番組のはてなエンジニア Advent Calendar 2021id:papixさんが紹介している内容と前提知識が同じだった!!そんなことってあるんだ

papix.hatenablog.com

あらためてApp::perlImports

  • どこから来たのかわからないシンボルをエクスポートしているuseを特定したい
  • @EXPORTを全部インポートするのではなく、一部だけ指定してインポートしたい

これらの課題に対して、golangのgoimportsを参考に、構文解析ライブラリPPIの力を借りて誕生したのがApp::perlimportsです。 metacpan.org

著者のOALDERSさんはLWPなどのメンテナもやられています。

使ってみる

例えば次の様なモジュールがあったとしましょう。

package MyPkg::One;

use strict;
use warnings;

use Exporter 'import';
our @EXPORT = qw(f1);

sub f1 {
  print "f1\n";
}

1;
package MyPkg::Two;

use strict;
use warnings;

use Exporter 'import';
our @EXPORT_OK = qw(f2);

sub f2 {
  print "f2\n";
}

1;
package MyPkg::Three;

use strict;
use warnings;

use Exporter 'import';
our @EXPORT_OK = qw(f3);

sub f3 {
  print "f3\n";
}

1;
package MyPkg::Four;

use strict;
use warnings;

use Exporter 'import';
our @EXPORT = qw(f4);

sub f4 {
  print "f4\n";
}

1;

これをhoge.plから以下の様に呼び出します。

use strict;
use warnings;
use MyPkg::One;
use MyPkg::Two;
use MyPkg::Three;
use MyPkg::Four;


f1();
f2();
MyPkg::Three->f3();

hoge.plの状況は次のとおりです。

  • f1はMyPkg::OneがEXPORTしているものを使っている
  • f2はMyPkg::TwoがEXPORT_OKしているが、コード上は指定を忘れている
  • f3はフルパスで呼び出している
  • f4はuseしているが特に使っていない

このコードはそのまま実行すると, f2の解釈で実行時エラーが発生します。また、どのモジュールからエクスポートされているのかパット見ではわかりません。 さらにMyPkg::Fourは必要がないf4をエクスポートしてしまっています。

❯ perl -Ilib hoge.pl
f1
Undefined subroutine &main::f2 called at hoge.pl line 10.

これをApp::perlimportsに通すと、以下のようにコードが書き換わります。

❯ perlimports --libs lib  hoge.pl
use strict;
use warnings;
use MyPkg::One qw( f1 );
use MyPkg::Two qw( f2 );
use MyPkg::Three ();
use MyPkg::Four ();


f1();
f2();
MyPkg::Three->f3();

見ると次の様な変換が行われています。

  • f1,f2がEXPORT元のモジュールが明記されるようになった
  • エクスポートした関数を使用していないThreeFourは、空配列を指定することになり、何もエクスポートしなくなっている

変換されたコードはそのまま動かす事ができます。(今回はパイプで動かしています) こうすると、実行時エラーを未然に防ぐことが可能になり、どのモジュールからエクスポートしているか、パット見でわかるようになりました。

❯ perlimports --libs lib  hoge.pl | perl -Ilib
f1
f2
f3

さらに、無駄にロードしているMyPkg::Fourを削除することも可能です。

❯ perlimports --libs lib --no-preserve-unused  hoge.pl
use strict;
use warnings;
use MyPkg::One qw( f1 );
use MyPkg::Two qw( f2 );
use MyPkg::Three ();


f1();
f2();
MyPkg::Three->f3();

こうすると、無駄なファイルをuseし続ける事もなくなり便利ですね!

インストール方法

cpanmもしくはcpmを使ってインストールしましょう

$ cpanm App::perlimports
$ cpm install -g App::perlimports
$ plenv rehash

使い方

CLIツールとして提供されているので、perltidyの様にコマンドで実行します。

$  perlimports --libs lib,local/lib/perl5 -f hoge.pl

主要なオプションを見てみます。

  • --libs
    • Perl-Iと同様。基本はlibを指定することになるでしょう
    • カンマ区切りです
    • Carton, cpmでinstallしたローカルディレクトリ上のモジュールも含めたい場合は、local/lib/perl5を加えます
  • -f
    • 変換対象のファイル名
  • --no-padding
    • perlimportsが変換したあと、モジュール名の前後に余計な空白をいれない
    • デフォルトではuse Foo qw( bar baz );の様になるが、--no-paddingを指定するとuse Foo qw(bar baz);になる
  • --inplace-edit, -i
    • デフォルトでは変換したコードは標準出力に出るが、このオプションを指定すると変換元のファイルを上書きする
  • --no-preserve-unused
    • 使っていなさそうなモジュールをuseしないようにする

注意点

自動でuseしてくれる訳ではない

下手にgoimportsを使ったことがあると誤解しやすい点で、perlimportsの(現状)の興味の対象はEXPORTしたシンボルをいい感じに特定することのみです。goimportsはimportの追加などもしていましたが、それに対応する機能はありません。モジュールのuseし忘れなどは興味の範囲外になっています。

例えばMyPkg::One->f1()の様なメソッド呼び出しをしていて、MyPkg::Oneをuseし忘れた場合、perlimportsはこれを補完しません。 また、そもそもuseしているモジュールがない状態で実行しても補完はしてくれません。

そのためモジュールのuse忘れなどのimportエラーは別系統で判断する必要があります。

❯ cat hoge.pl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/lib";

use DDP {deparse => 1, hash_max => 0};

f1();
~/workspace/test3 via 🐪 v5.34.0
❯ perlimports --libs lib -f hoge.pl
use strict;
use warnings;
use FindBin ();
use lib "$FindBin::Bin/lib";

use DDP {deparse => 1, hash_max => 0};

f1();

関数エクスポート以外のuseは工夫の余地がある

また、Class::Accessor::LiteClass::Enumemonのようなuse時にエクスポートする関数名を指定する以外に、独自にimportsを定義しているモジュールは、perlimportsはuseしたものを使っていないと見なしてしまいます。これはperiimportsの作者によって名指しでperlimportsの自動整形下に置かない様に書かれているモジュール群か、構文解析の結果自動調整を逃れたもの以外は、空配列のimportに変換されてしまいます。

❯ cat hoge.pl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/lib";

use DDP {deparse => 1, hash_max => 0};
use Class::Accessor::Lite (
    rw => [qw/ hoge/],
);

f1();


~/workspace/test3 via 🐪 v5.34.0
❯ perlimports --libs lib,local/lib/perl5 -f hoge.pl
use strict;
use warnings;
use FindBin ();
use lib "$FindBin::Bin/lib";

use DDP {deparse => 1, hash_max => 0};
use Class::Accessor::Lite ();

f1();

これを回避するには、調整したくないモジュールをuseしている行の後ろに特定のアノテーション(## no perlimports)を書くか*3コマンド実行時の引数でモジュール名を直接列挙、またはモジュール名が列挙されたファイルを渡すことで回避可能です。この指定には正規表現が使用可能です。

作者にPRを送るとignoreするモジュールに追加してくれる様なので、普段使うモジュールで管理されるとまずいものがあればPRを送ると良いでしょう。

~/workspace/test3 via 🐪 v5.34.0
❯ cat hoge.pl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/lib";

use DDP {deparse => 1, hash_max => 0};
use Class::Accessor::Lite (
    rw => [qw/ hoge/],
);

f1();
~/workspace/test3 via 🐪 v5.34.0
❯ perlimports --libs lib,local/lib/perl5 --ignore-modules-pattern '^Class::' -f hoge.pl
use strict;
use warnings;
use FindBin ();
use lib "$FindBin::Bin/lib";

use DDP {deparse => 1, hash_max => 0};
use Class::Accessor::Lite (
    rw => [qw/ hoge/],
);

f1();

メソッドの追加などの副作用で持つモジュールをuseしている場合は、--no-preserve-unusedに気をつける

Perlのモジュールでは、useすると自分以外のモジュールのメソッドをオーバーライドやメソッドを名前空間に追加するものがあります。 例えばHTTP::Message::PSGIは、useするとHTTP::Request名前空間to_psgiがメソッドとして追加されます。

これはuseすることで効果を発揮するモジュールであるため、HTTP::Message::PSGIがEXPORTしているメソッドを直接使っていないケースや、HTTP::Message::PSG->で何かを呼び出しているコードが無い限り、perlimportsで--no-preserve-unusedオプションを付けて実行してしまうと、何も意味がないモジュールを呼び出しているとuseを削除されてしまいます。

実際はHTTP::Message::PSGIはperlimports側で明示的に整形する対象に含まないように指定されているので、この様なことは起きないのですが、自分たちで書いているライブラリに似た挙動があるものがあれば、使用する際は気をつけましょう。


明日はid:mp0liiu さんで、immutableなコレクションを作るです!

*1:動的ロードなどを考慮するとこの限りではない

*2:awkから来ていた気がする

*3:ブロック的に囲うことも可能です

https://metacpan.org/pod/perlimports#ANNOTATIONS/IGNORING-MODULES

ラズパイにgolangで書かれているdocker composeをinstallする

多分令和最新版。

linuxでdocker composeを使いたい場合、macOSwindowsDocker for xxx系とは違い、docker composeはバンドルされてないので、自分でインストールする必要があった。

特にラズパイではdocker-compose時代(Pythonスクリプト)の場合はpipでインストールする必要があった。 我々はdockerでローカルのことを考えずに動かしたいのにPython3関係の構築をしないといけないのは面倒....。

現代ではdocker composeはgolangで書き直されており、これを使う場合はpip installが不必要。 さらにARMバイナリ用のdocker composeはGitHub Releaseで配布されているので、落としてくれば使えるという極めて親切な設計になっている。

今回はすでにdockerはインストールされているものとして、docker composeのインストールを見てみる。

インストール方法

基本はcomposeのREADMEに書いてあるとおり。

github.com

自分だけで使う場合は、$HOME/.docker/cli-pluginsに、全ユーザーで使いたい場合は/usr/local/lib/docker/cli-pluginsなどにdocker-composeの名前でバイナリをポン置きすれば使えるようになる。

バイナリはリリースから持ってくれば良い。

github.com

気をつける点として、uname -mすると微妙にarmv7lとcomposeで打たれているアーキテクチャ名(armv7)と異なる。ワンライナーの中でuname -mしていい感じに取得しようとする場合は注意。

$ uname -m
armv7l

自宅のラズパイ4で、全ユーザーにインストールしたい場合は次のような感じでインストールする。

とりあえずrootになり

$ sudo -i 

後は素朴に

# mkdir -p  /usr/local/libexec/docker/cli-plugins
# cd /usr/local/libexec/docker/cli-plugins
# wget https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-linux-armv7 -O docker-compose
# chmod +x docker-compose

こうするとdocker composeが使えるようになる。

$ docker compose

Usage:  docker compose [OPTIONS] COMMAND

Docker Compose

Options:
      --ansi string                Control when to print ANSI control characters ("never"|"always"|"auto") (default "auto")
      --compatibility              Run compose in backward compatibility mode
      --env-file string            Specify an alternate environment file.
  -f, --file stringArray           Compose configuration files
      --profile stringArray        Specify a profile to enable
      --project-directory string   Specify an alternate working directory
                                   (default: the path of the Compose file)
  -p, --project-name string        Project name

Commands:
  build       Build or rebuild services
  convert     Converts the compose file to platform's canonical format
  cp          Copy files/folders between a service container and the local filesystem

バイナリポン置きなのでローカル環境に色々いれなくていいので便利ですね。現場からは以上です。

入社してから書いていた分報の行数を眺めてみる

この記事ははてなエンジニア Advent Calendar 2021 の4日目の記事です。

昨日は同期のid:gurriumくんでした。MOD Pythonで色々できるんだ...。僕も趣味で好きなゲームに色々コード書いて楽しんでますが、それは社のアドベントカレンダーには書けないゲームなのでまたの機会に書こうと思います。

社ではScrapboxを使っていて、僕は分報の置き場として色々書いています。個人的にはTwitter感覚で使っています。 分報は以下のフォーマットで基本書いています。

anatofuznote 2021/11/29 - 12/03
[anatofuznote 2021/11/22 - 11/26]  <=> [anatofuznote 2021/12/06 - 12/10]

#anatofuz #anatofuznote
[** 2021/12/3]

実際には↓の用な感じでScrapbox上は見れる f:id:anatofuz:20211204183001p:plain

忙しいときはツイート数が減っている気がしていて、逆になんか盛り上がっているときはツイート数が多いかなとなんとなく感じています。 つまりツイート数で自分の状態が測れるのではと思っています。 せっかくなので、本当にそうなのか調べてみました!!! 分報なのでツイート数==scrapboxの行数として考えてみます。

準備

APIを使ってかっこよくやるとかは頭になく、素朴にざっくりと外観が知られればいいので、雑スクリプトで集計します。 分報は先程例示したフォーマットで書いているので、[** 2021/%m/%d]に囲まれている間の行数を雑にカウントする方法でやってみます。

データはどうやって持ってくるかというと、コピペですね。はい。素朴にやるのが早いねんな。 ということで次のようなPerlスクリプトを書きました。

use strict;
use warnings;
use Time::Piece;

my $current_date = undef;
my $result = {};

while (my $line = <DATA>) {
  if ($line =~ /^\A\s*\[\*.+\s*(2021.*)\]/) {
      $current_date = $1;
  }
  $result->{$current_date}++;
}


my @sorted_date = sort {  Time::Piece->strptime($a, '%Y/%m/%d') <=>  Time::Piece->strptime($b, '%Y/%m/%d') } keys %$result;

for my $date (@sorted_date) {
    print "$date $result->{$date}\n";
}


__DATA__
[** 2021/12/3]
  今日も
  1日
[** 2021/12/2]
   オハヨッ
....

Perl__DATA__以下に書いたテキストを<DATA>で読めるので、Scrapboxからかき集めた分報をDATA以下にコピっています。 それを素朴な集計方法で数え上げて、最後に日付順にソートして表示します。

実行するとこんな感じ

❯ perl note.pl | head -4
2021/4/8 503
2021/4/9 11
2021/4/12 260
2021/4/14 256

これをグラフ化したいので、学生時代から慣れ親しんでいるgnuplotでシュッとグラフを作ります。matplotlib? 俺はgnuplotのほうが慣れてるんだ...。

❯ gnuplot

        G N U P L O T
        Version 5.4 patchlevel 2    last modified 2021-06-01

        Copyright (C) 1986-1993, 1998, 2004, 2007-2021
        Thomas Williams, Colin Kelley and many others

        gnuplot home:     http://www.gnuplot.info
        faq, bugs, etc:   type "help FAQ"
        immediate help:   type "help"  (plot window: hit 'h')

Terminal type is now 'qt'
gnuplot> set term svg

Terminal type is now 'svg'
Options are 'size 600,480 fixed enhanced font 'Arial,12' butt dashlength 1.0 '
gnuplot> set output 'out.svg'
gnuplot> set xdata time
gnuplot> set timefmt '%Y/%m/%d'
gnuplot> plot '<perl note.pl' using 1:2 with line
gnuplot> exit

ということでできたのがこれです。

GnuplotProduced by GNUPLOT 5.4 patchlevel 2 0 100 200 300 400 500 600 04/01/21 05/01/21 06/01/21 07/01/21 08/01/21 09/01/21 10/01/21 11/01/21 12/01/21 01/01/22 '<perl note.pl' using 1:2 '<perl note.pl' using 1:2

見ていると入社当時は盛り上がっていたものの、9月から口数が少なくなり、11月は一般人みたいなツイート数になってましたね..。 最近ツイート数が上がってきているので盛り上がってきているという感じなきがする。 初期が長めなのは環境構築のログがはられていたりするからっぽかった。

可視化してみると色々見れて便利ですね。ここまで書いてmackerelのサービスメトリックに日毎に送信すると面白いのでは? みたいな気持ちになったので、調べたところ、過去のデータは送信できなさそう? なので次回にご期待ください。

明日はid:mizdraさんです。

調子が悪いときの傾向と対策

最近なんか調子悪かったんですがようやく回復してきたので自覚している調子の悪さについて書いておこう。。。

症状

  • 口数が減る
    • ツイート数が減る
      • 分報を書かなくなる
    • 頭の中は色々考えているがアウトプットする気力が減っている
      • アウトプットに対してネガティブな反応をもらうと立ち直れなくなるなる状態になってるので本能的にしなくなるのだろう
    • でかいタスクをどーんとやって死ぬみたいなのがでる
  • 相談の内容がうまくまとまらない感じで相談する
    • まとめるほどの気力がない
    • まとめるエネルギーと時間がないので頭の中のごちゃごちゃした解像度で会話をする
  • 言語化が全般的にできなくなる
  • ちょっとしたことで傷つき出す
    • ミーティング時の状態などで「あぁ....」みたいになり悲しい気持ちになる
    • 寝る前や土日にそのことばかり考えてしまう
  • 常に業務のことを考えている
    • 土日と退勤後と出勤前もギョームのコードや情報を見ている
    • 楽しくて見ているのではなく不安に駆られてみている
  • よく考えればできること、普段できることができなくなる
    • 質問の仕方とPRの説明分の情報量をごちゃまぜにして処理してしまう
    • 普段なら気にせずできる作文などができなくなってくる
    • おちついてデバッグすればわかる問題がわからなくなる
  • ものをこぼしたり落としたりするようになる
  • 不安感にさいなまれる
    • なんか出勤したら大声で紛糾されるんじゃないかみたいな恐怖を考えてしまう(完全に調子悪いとき)
    • なんか裏で自分のことについて話されているのではないか、責められているのではないかとなる
  • 作業がバッティングする
  • 同じ行を書いたり消したりする
  • 金の出費が激しくなる
    • DLsiteとDMMで色々買う
    • 月末のカードの請求でさらに調子が悪くなる
  • 新しいコンテンツが全般的に受け付けなくなる
    • 音楽はだいたい同じプレイセットをリピートしたり、動画もよく見る人の投稿ばかりみる
    • 映画館に行って新しいコンテンツを見る元気がなくなる
  • 朝起きれなくなる
  • 退勤後何もできなくなる
  • 突然よくわからないLINEを知人に送る

対策

  • 有給をとっても気持ちが休まらないので仕事はする
  • 読書をする
    • 好きなホラーを読む
  • 紅茶(アールグレイ)を濃いめに入れて飲む
    • 他の味のティーパックだと気持ち悪くなる
  • 深く考えずできる単純作業をする
  • たまに好きな言語でプログラミングする
  • ポモドーロテクニックで切り替える
  • どうせ仕事は頑張るので頑張りすぎないことを意識する
    • とはいえ「もう少し頑張れるのでは...」となりがちなので難しい
  • 1日の情報量/思考量を減らす
    • あわあわしているときに毎日仕事の着手順を考えてたり他の進捗を考えているとリソースがだいぶ持っていかれる
    • 今日しないといけないことだけ見ておく
    • タスク順を他の人に考えてもらう
  • 焼き肉に行く
    • 思えば去年は安々行きまくってたな...
  • 水辺に行く

少し余力が出てきたタイミングでまとめるとまぁこういう感じだったんだなーとなりますね。自覚症状以外にもいろいろありそう

TypeScriptで関数合成をする関数を書いてみた

関数型プログラミングといえば関数合成みたいなところありますよね。Haskellだと.でagdaだとoだった気がする。

最近やっているTypeScriptで関数合成をやってみました。

以下はググって出てきたこのサイトを参考にしています。 minaluke.medium.com

まず例題のために簡単なInterfaceを定義してみます。 nameというフィールドがあってstringが入っています。

interface IPerson {
  name: string;
}
const person: IPerson = {
  name: "Mina",
};

ここから次の関数を順に実行することを考えます。 処理としてはgetNameでIPerson型からnameのstringだけ取り出して、その結果をgetLengthに流して文字列長を取得。 その結果が偶数かどうかを判定する感じですね。

const getName = (p: IPerson) => p.name;
const getLength = (str: string) => str.length;
const isEven = (num: number) => num % 2 === 0;

合成関数使わずに純粋に書くとこんな感じだと思います。

console.log(isEven(getLength(getName(person))))

これでも良いんだけど関数が多いと書くのがしんどい!!!! ので合成して一つの関数にまとめることを考えます。

そこで関数合成をする関数composeを考えます。 Javascript的な型を考えないと次のようなコードになりそうです。

const compose = (...fns) => arg =>
      fns.reduce((composed, f) => f(composed), arg);

引数として関数の配列fnsを受け取って、reduceで部分適用して進めていく感じですね。 これをTypeScript化することを考えると、fnsargに型を指定する必要があります。 それで実際に書いてみると次のようになりました。

const compose =
  (...fns: ((arg: any) => any)[]) =>
  (arg: any) =>
    fns.reduce((composed, f) => f(composed), arg);

...fnsは「なにかしらの引数を受け取って何かしらの返り値を返す関数 ( (arg:any) => any)の配列」なので((arg: any) => any)[]となり、arg自体は何かしらの型なのでanyになります。 書いてみるとまぁそうだよねという感じですね。多分いい感じのライブラリがありそうなので情報お待ちしております。

ApolloServer/Clientのフルスタックチュートリアルやった

最近はGraphQLの時代!!!!! ということでApolloGraphQLのフルスタックチュートリアルをやりました。

www.apollographql.com

構造自体は1-4まではApolloServer側の話で、6-9がApolloClient側の話。 ServerからやっているとGraphQLサーバーの実装から出来てお得という感じ。

例題としてはSpaceXの無料APIをApolloServer側でラップしてGraphQLにして返すのがファーストステップとなっている。

github.com

その後はミューテーションとしてログインや宇宙旅行の予約をServerで実装する。 テストはApollo StudioっぽいGUIの画面で実際にQueryやMutationが返ってきているかを確認する感じ。 リポジトリの雛形実装はPureなJavaScriptだったので、定期的にフィールドの大文字小文字をミスって時間が溶けそうになるなどがあった。 ここだけ別言語で実装してみてもいいかもしれない。

6移行はApolloClientのチュートリアルで、TypeScriptとReactを使っていい感じに宇宙旅行のwebサイトを作っていく。 とはいえあくまでApolloClientのチュートリアルなので、各種componentはすでに実装されているものを使っていく感じ。 componentを作る感じは別のチュートリアル/書籍をやれという感じだけど、ApolloClientで共通した処理の書き方や、Query、Fragmentの書き方などがわかった。

特にPagination関連の実装は自分で手を動かしてみると処理の雰囲気がなんとなく理解できてきて良かった。 ClientだとfetchMore受け取ってcursorいじって再度リクエストするのはなるほどねーとなっていた。

とはいえかなりコンポーネント関係お膳立てされているので素朴にアプリケーション組んでみたほうが良さそうな気がする。 アプリケーション自分で書く前の助走としてはいい感じに出来たかな。また見返したい。

dein vimでプラグインをupgradeする

何かしらのbrewの更新でしくったのか。vimを起動しようとするとfugitive.vimがエラーするようになった。

/Users/anatofuz/.cache/dein/.cache/.vimrc/.dein/plugin/fugitive.vim の処理中にエラーが検出されました:
行  471: E1208: -complete used without -nargs行  472: E1208: -complete used without -nargs行  476: E1208: -complete used without -nargs行  479: E1208: -complete used without -nargs続けるにはENTERを押すかコマンドを入力してください

fugtive.vimはgitをvimから操作するプラグイン。実はあまり使っていない。

github.com

プラグインでなくてvim本体の問題かと思って、vim自体をupgradeしたけれど状況変わらず。 う〜んと思っていたところ、vimプラグイン管理で使っているdeinでupgradeすればいけるのではと思いやってみた。 upgrade自体は死ぬほど簡単で、vimを開いてコマンドモードでcall dein#update()を実行するといい。

upgradeしたところ無事に起動出来た。fugitiveの最新版で治った説もあるけれど、upgrade時に依存ライブラリが解決されたとかな気がする。