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

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

git grepの代わりにgit agを使う

git grepが便利なので,同じ感覚でag (The Silver Searcher)を使ってみたいという話です.何事も速いほうが良い.

前提

ぶっちゃけagは,デフォの状態で.git/以下の内容や.gitignoreに書かれてるファイルやディレクトリなんかを検索の対象から排除するのであんま旨味は無い.

方法

以下のエイリアスを張る *1.もちろんconfigファイルを直で編集しても良いです.

$ git config alias.ag '!git ls-files | xargs ag'

git ls-filesを使って,gitで管理されているファイルの一覧を持ってきて,xargsを使ってagに渡してやるという感じ.

実際僕はこれで十分なんですが,表示と挙動をgit grep(1)っぽくしたい場合は以下のようになるでしょう.

$ git config alias.ag '!git ls-files | xargs ag --pager="less -F -R" --nogroup --color-match=0\;31 --color-path=0\;0 --no-numbers'

--pager="less -F -R"はお使いのpagerに応じて変更するか,あるいはお好みに応じてpagerオプションを取り去ると良いでしょう.

結論

$ git ls-files | xargs $COMMAND

が基本的に便利!!!!!

*1:場合によって--globalオプションなどを付けても良いでしょう

Released Perl::Lint as underdevelopment

(日本語は下にあります / Japanese follows English)


I've just released Perl::Lint as underdevelopment.
https://metacpan.org/release/MOZNION/Perl-Lint-0.01_01


Development of Perl::Lint is in progress, but I need feedback about this module.
Thus I released it on CPAN to simplify installation in spite of it is on the way.
(Thank you for your advice, @tomhukins and @)


Installation is easy.

$ cpanm --dev Perl::Lint


And usage is simple!

use Perl::Lint qw/lint/;
my $violations = lint(['path/to/file.pl']);


This module is unfinished as you know.
Please don't believe this and me!!

I'm looking forward to hearing your feedback.
Please send your mind to the GitHub Issues or @.

Enjoy!


P.S.
I'm going to talk about Perl::Lint at YAPC::Asia Tokyo 2014.
Let's talk me at there!!



Perl::Lintを開発版としてリリース致しました.
https://metacpan.org/release/MOZNION/Perl-Lint-0.01_01


Perl::Lintは目下のところ開発中のモジュールですが (まだ出来てねえのか!) ,そろそろユーザのフィードバックが欲しいなあとか,Tom Hukins氏や@氏らから「とりあえずリリースしちゃえよ! インストールしにくくてかなわん!」という意見を頂いたとか,そういった事情でunderdevelopment releaseを敢行したというのが背景です.


インストールは至って簡単です.

$ cpanm --dev Perl::Lint


使い方もシンプルです.

use Perl::Lint qw/lint/;
my $violations = lint(['path/to/file.pl']);


ご存知の通りこのモジュールは未完成です.
このモジュール (の吐き出す結果) と俺を信用しないでください.

フィードバックはGitHub Issuesに書いてもらえるか@まで直でメンション飛ばしてもらえると嬉しいです.

よろしくお願いします.


[追記]
そういや言い忘れてましたが,YAPC::Asia 2014というイヴェントでPerl::Lintの話をします.
会場でお会いしましょう!!!

先読みとautovivificationの話,あるいはマイクロオプティマイゼーションの話

Perlの話です.が,先読みの辺りはどの言語でも共通なのでは,という感じです.

追記

なんか先読み関係ない感じになってるのでコメント見ると良いです.この記事の情報は誤っているので後で書き直す.

ここから先は読まなくても良い

さて,配列を走査するような処理を書く時,「1個後のアイテムと現在のアイテムを比較したい」というようなニーズから,1個後のアイテムを読んでおきたいというような事があると思います.

ナイーブに実装するとしたら以下のようになるでしょう (C-Style Loopで書いている理由は以降の比較のためなので深く考えないで下さい).

my @array = (1..100);
for (my $i = 0; my $item = $array[$i]; $i++) {
    my $next_item = $array[$i+1];
    # do something...
}

このコードは,先読みした値を変数に格納しておいて,それを使いまわすというような効率的な書き方に変形する事が出来ます.

my @array = (1..100);
for (my $i = 0, my $next_item; my $item = $next_item || $array[$i]; $i++) {
    $next_item = $array[$i+1];
    # do something...
}

さてこの両者でベンチマークを取ってみると以下の様な感じになります.

use Benchmarks sub {
    my @array = (1..100);

    my $normal = sub {
        for (my $i = 0; my $item = $array[$i]; $i++) {
            my $next_item = $array[$i+1];
            # do something...
        }
    };

    my $read_ahead = sub {
        for (my $i = 0, my $next_item; my $item = $next_item || $array[$i]; $i++) {
            $next_item = $array[$i+1];
            # do something...
        }
    };

    +{
        normal     => $normal,
        read_ahead => $read_ahead,
    };
};
__END__
              Rate     normal read_ahead
normal     39711/s         --       -25%
read_ahead 52609/s        32%         --

速いですね! めでたしめでたし!

めでたくない場合

しかしながら走査するオブジェクトの構造が変わるとそうは問屋がおろしません.
以下の様なコードの場合を考えてみましょう.

use Data::Faker;
my $faker = Data::Faker->new;
my @array;
for (1..100) {
    push @array, +{
        name => $faker->name,
        age  => int rand(80),
    };
}

for (my $i = 0, my $next_item; my $item = $next_item || $array[$i]; $i++) {
    $next_item = $array[$i+1];
    $next_item->{name};
}

このコードは永遠に終わりません.無限ループします.

このコードで配列の最後の要素にアクセスしている時,$next_itemundefとなりますが,このundefに対してハッシュリファレンスのようにアクセスすると,undef{}という風に空のハッシュリファレンスに変化してしまいます (もちろん配列リファレンスで同じようなことを行なった場合も同様です).以下の様な感じ.

my $foo = undef;
$foo->{not_exists}; # <= この時点で$fooは{}になっている!

Perlでは空のハッシュリファレンスは真値であるので,論理和の評価は空ハッシュである$next_itemが返ってきてしまい,for文の条件が常に真として扱われてしまい無限ループしてしまうという訳ですね.

このように,走査対象の配列の中身がハッシュリファレンスで,なおかつ1個次の要素を見る必要がある場合,上手くいかずにハマる場合があるわけです.

そこで我々はどうすべきか

1. ローカル変数を1個作って,そこに$next_itemをコピーしてそっちにアクセスする
for (my $i = 0, my $next_item; my $item = $next_item || $array[$i]; $i++) {
    $next_item = $array[$i+1];
    my $_next_item = $next_item;
                                                                            
    # do something...
    $_next_item->{name};
}

このようにすると無限ループは起きないものの……

use Data::Faker;
use Benchmarks sub {
    my $faker = Data::Faker->new;
    my @array;
    for (1..100) {
        push @array, +{
            name => $faker->name,
            age  => int rand(80),
        };
    }

    my $normal = sub {
        for (my $i = 0; my $item = $array[$i]; $i++) {
            my $next_item = $array[$i+1];

            # do something...
            $next_item->{name};
        }
    };

    my $read_ahead = sub {
        for (my $i = 0, my $next_item; my $item = $next_item || $array[$i]; $i++) {
            $next_item = $array[$i+1];
            my $_next_item = $next_item;

            # do something...
            $_next_item->{name};
        }
    };

    +{
        normal     => $normal,
        read_ahead => $read_ahead,
    };
};
__END__
              Rate read_ahead     normal
read_ahead 19855/s         --       -11%
normal     22399/s        13%         --

先読みしたほうが遅い!!!! 意味無いじゃん!!!!
……まあそりゃそうですよねという感じ.

2. no autovivificationする

undef->{something}->[42]のようにアクセスするとそれぞれ空の{}[]という風になってしまうのが問題なので,その挙動を潰してやれば良い!

ということでautovivificationの出番です.
https://metacpan.org/pod/autovivification

no autovivificationという風に宣言してやると,そのレキシカルスコープでvivification,つまりundefを自動で空のハッシュリファレンスや配列リファレンスに変換する処理を無効にすることが出来ます.

no autovivification;
for (my $i = 0, my $next_item; my $item = $next_item || $array[$i]; $i++) {
    $next_item = $array[$i+1];
    # do something...
    $next_item->{name}; # <= ここでundefが変換されない!
}

ベンチマークの結果は以下のとおり

no autovivification;
use Data::Faker;
use Benchmarks sub {
    my $faker = Data::Faker->new;
    my @array;
    for (1..100) {
        push @array, +{
            name => $faker->name,
            age  => int rand(80),
        };
    }

    my $normal = sub {
        for (my $i = 0; my $item = $array[$i]; $i++) {
            my $next_item = $array[$i+1];
            # do something...
        }
    };

    my $read_ahead = sub {
        for (my $i = 0, my $next_item; my $item = $next_item || $array[$i]; $i++) {
            $next_item = $array[$i+1];
            # do something...
        }
    };

    +{
        normal     => $normal,
        read_ahead => $read_ahead,
    };
};
__END__
              Rate     normal read_ahead
normal     36885/s         --       -18%
read_ahead 44776/s        21%         --

おっ,速い!

まとめ

  • undefにリファレンスアクセスするといつの間にかundefが別のものになる
  • うかつに先読みしない
  • とは言え先読みしたい,なおかつ少しでもパフォーマンスが気になる場合はno autovivificationを使うのが良いのでは無いか.


という感じです

Table要素をCSVとかXLSXとかに変換して保存出来るChrome拡張書いた

皆さん年に2,3回は,「ウェッブページのTable要素をXLSX (もしくはCSV) に変換して保存したいワ!」となることがあると思います.僕はあります.

そういう時は,「心をこめてTableをコピーしてExcel (もしくはお好みのヒョーケーサンソフト) にペースト!」みたいな感じになると思うんですが,コピペギョームはTableの行数がめっちゃ多い時 (1画面で収まらないTable要素とか) にだるいし,コピペ処理中になんらかの事故が起きてミスった結果えらい人に呼ばれて怒られる可能性なども否めないので,そういうことをクリック2発で実現するChrome拡張を書きました.

https://chrome.google.com/webstore/detail/table-to-spreadsheet/haidhlbpihfihbjcggmffnmhgiddjcoc?hl=ja

何をするChrome拡張かというと,Table要素を右クリックして,XLSX (あるいはCSV) に変換するメニューを選択すると,Tableの内容がそれらの形式に変換されてローカルに保存できるというシンプルな拡張となっております.

色々あって,Table以外の部分でもコンテキストメニューが表示されてしまう感じになっていますが,Table以外の箇所でメニューを選択してもalertが出るだけという親切設計 (!?) になっているので安心 (!!??) です *1
あと既知の問題として,テーブルの中にテーブル,というような入れ子構造のテーブルに上手く対応できないというものがあって,これはどうしたもんかなあと思っております.何か良いアイデアがある方がいらっしゃいましたら教えて下さい.


とりあえずなんやかや便利な感じなのでご利用下さいませ.


なおソースは以下です.CSV.jsjs-xlsxがトンデモなく便利でヤバい!
https://github.com/moznion/crx-table-to-spreadsheet

*1:ダサいからなんとかしたい

ページャNight 1ページ目という勉強会やりました


録画したustの様子はこちら

http://www.ustream.tv/recorded/49544381


ページャNight <[1]> on Zusaarというイベントを開催致しました.
実に冗談みたいな理由から興ったイベントでしたが,ページャ (ページネーション) というWebサービスの1コンポーネントに焦点を絞った非常に濃厚かつ興味深いトークの数々を聞くことができて,非常に良い勉強会になったと思います.

以下,発表の一覧です.

15分トーク

@さん お前自分ちのページャUIが本当に速いと思ってんの?


@さん APIのページングの話


@さん ページャ専用DBのご提案


@さん ふつうのページャーをつくろう


LT

@さん 前に進むだけがページャじゃない
@さん ページラング


発表してくださった皆様,お集まり頂いた皆様,そして快く会場を提供してくださったピクシブ株式会社の皆様,ありがとうございました!

さて

Webサービスを構築する上で今回の「ページャ」のようなコンポーネントはそれ単体では小さな存在の為,それらを主として知見を共有したり議論したりするという機会にはあまり恵まれていないように感じています.
どっこいWebサービスというのはこうした小さな・細かなコンポーネントを根気よく組み合わせて構築していくものなので,小さな存在だからと言って蔑ろにしてはならない.Webサービスを構成している要素は,その規模の大小に関わらず等しく重要なポジションを占めているので,それら個別に関する勉強会があっても良いじゃないか! という動機で今回のイベントを催しました.結果的に数々の知見がお披露目され,非常に有益な会になったように思います.

こうした「細かいコンポーネントシリーズ」はコンスタントに開催できれば面白いのでは,と思った次第.

またお会いしましょう.

【補欠の方も参加いただけます】 今週金曜日,7月4日はページャNightです

参考

http://www.zusaar.com/event/5477013
http://moznion.hatenadiary.com/entry/2014/06/11/163303

今週金曜日はページャNightです.会場はピクシブ株式会社様です.
たくさんの方に発表していただけることとなり,当初の想定の100倍くらい実りのある会となりそうであります.

そしてなんと!!!

ピクシブ様のご厚意で補欠の方でも立ち見で参加頂ける運びとなりました!!
ぜひふるってご参加下さいませ.

Time::SecondsのONE_MONTHとONE_YEARについて

Time::SecondsONE_MONTHONE_YEARを使う場合,本当にその方法で良いのかよく考えたほうが良いと思います.バグが出る可能性が高い気がします.

例えば以下の様な場合

use Time::Piece;
use Time::Seconds;

my $tp = localtime->strptime("2014-04", "%Y-%m");
say $tp->mon; # => 4
say $tp->ymd; # => 2014-04-01

$tp -= ONE_MONTH;
say $tp->mon; # => 3
say $tp->ymd; # => 2014-03-01

$tp -= ONE_MONTH;
say $tp->mon; # => 1 (!)
say $tp->ymd; # => 2014-01-30 (!!)

このコードは,Time::PieceのオブジェクトからONE_MONTHを引いてやる事によって1ヶ月前の月を表現したいという意図を表していますが,上手く動きません.
それもそのはず,Time::Secondsの実装は以下のようになっており,

https://github.com/rjbs/Time-Piece/blob/master/Seconds.pm#L29-L31

ONE_MONTHは2629744秒 (つまりONE_YEAR / 12) のようにして定義されています.
30日を秒に表すと60 * 60 * 24 * 30 = 2592000秒だからONE_MONTHよりも小さいわけですね.
加えて2014年2月は28日しか無く,つまり60 * 60 * 24 * 28 = 2419200秒なので,ここで決定的に差が生まれてしまったという訳ですね.これは具合が良くない! もちろん「1ヶ月前の今日」みたいなものも上手くは取れないですね.

そしてONE_YEARONE_YEARで31556930秒,つまり365.24225日として表現されている為,こちらも普通に使うとおかしな事になってしまいます.

use Time::Piece;
use Time::Seconds;

my $tp = localtime->strptime("2014", "%Y");

$tp -= ONE_YEAR;
say $tp->year; # 2012 (!)
say $tp->ymd;  # 2012-12-31 (!!)

のっけからやってくれる!!!! まあそれはそうですよね,という感じ.
こちらも「1年前の今日」みたいなものは上手く取れない.


で,どうするかというと,Time::Piece::Monthを使うと言った方法が考えられますが,このモジュールは内部でDate::Simpleを使っていて,Date::Simpleは呪われている(呪われていた)という問題があるので少し渋い.この方法でも問題なさそうだったらこれで良いとは思います.
問題が複雑では無かったら,LEAP_YEARNON_LEAP_YEARONE_FINANCIAL_MONTHや,あるいはONE_REAL_MONTHONE_REAL_YEARを駆使して自分で組み立てるというのも1つの手かもわかりません.

とにかく,ONE_MONTHONE_YEARを使って何らかの日付け操作をしているコードを見つけた時は疑ってみたほうが良いと思います.


手が空いたらここらへんをケアするシンプルなモジュールを書くのもやぶさかではありませんが,果たして手が空くのか!


[追記]
Time::Piece::Plus使えばええんや!!!!! という僕の中の僕が囁きましたのでシェアします.

[追記]
id:karupanerura氏「add_months/add_years ってメソッド使えや!!!」
とのこと.なんで僕はこのメソッドの存在を知らなかったのだろうか!?

ともかくONE_MONTHとかONE_YEARとかつかってたら大体おかしいことには違いないので,そういう場合は疑うってことでひとつ.