Typescript & Immutable.jsで快適データ構造操作

今回は、業務で使っているTypescriptと、javascriptのデータ構造ライブラリであるImmutable.jsの合わせ技についてご紹介したいと思います。

Typescriptとは

MicroSoft社が開発を始め、今やGithubOSSとして開発が続けられているAltJS(JSにコンパイルできる言語)の一つです。

TypeScript - JavaScript that scales.

この言語の一番の特徴といえば、静的な型付けによるコンパイル時型チェックです。Typescriptにはnumber,string,booleanなどのプリミティブ型を始め、Java等で馴染みのあるInterfaceや、Classも存在します。また、ジェネリクスによる型のパラメータ化もできます。

もちろん、コンパイル後にはこれらの情報はほぼ落ちてしまうのですが、型チェックの恩恵が受けられるので、主にプログラミングの際の補完がかなり優秀です。今回はこの補完という要素についてfocusしてみたいと思います。

Immutable.jsとは

https://facebook.github.io/immutable-js/

こちらは、javascriptにおけるイミュータブルなデータ構造を提供してくれるライブラリです。プリミティブな実装にもあるArrayやMapの他にも、SetやListなんかもあります。 javascriptは組み込みのデータ構造操作メソッドがあまり充実しておらず、またimmutable/mutableの区別が曖昧なものが多いです。このライブラリは、その問題を解消し、元データに手を加えることなく様々な操作を可能とします。

Immutable.jsとjavascriptの相性

さて、このImmutable.jsですが、ドキュメントを見てみるとなにやらjavascriptらしくない(?)型要素が満載のドキュメントになっています。 C++Javaなどに慣れている人ならばピンと来るのですが、ここで使われているのはジェネリクスというものです。 たとえばMap<K, V>という型は、Key(連想配列の添字)がK型、値(連想配列の中身)がV型であるということを表しています。ここでKとVは型を表すパラメータとして使われています。 そんなもの動的型付けの前じゃ意味ないじゃないか!という意見はごもっともです。実際javascript上では、この型付けによる恩恵はほぼありません。 (とはいえ、リスト操作に関しては組み込みのものよりも多くのメソッドがあり、またImmutable性を保証してくれるので使うモチベーションは大いにあります。)

型付けによる恩恵

もちろんImmutable.jsの型定義ファイルは公開されており、Typescriptでこの型の恩恵を大いに受け取ることができます。コンパイル時チェックもさることながら、コーディング中の補完もかなり優秀です。

たとえば、Aくんが以下のようなプログラムを書いたとします。中身はとても適当なのですが、とりあえずリストに対して複雑な処理がしたいということだけ察してください。

// user.ts
interface User {
    id: number;
    name: string;
    friends: User[];
}

export default User;
// main.ts
import User from './user';
import * as Immutable from 'immutable';

let bob: User = {
    id: 1,
    name: "Bob",
    friends: []
};

let alice: User = {
    id: 2,
    name: "Alice",
    friends: [bob]
};

let john: User = {
    id: 3,
    name: "John",
    friends: [bob]
};

let eliza: User = {
    id: 4,
    name: "Eliza",
    friends: [alice]
};

let users: User[] = [bob, john, alice];

Immutable.Seq(users)
    .sortBy((user) => user.id)
    .map((user) => {
        user.friends.push(eliza);
        return user;
    })
    .filter((user) => user.name.length > 4)
    .reverse()
    .last();

上記main.tsにおける最後のメソッドチェーンの結果はUser型であることが期待されますが、書いた後のAくんにはほんとにUser型が返ってくるかどうかわかりません。型エラーがあれば最終的にコンパイル時にわかるのでよいのですが、書いたそばからコンパイルするのは面倒です。本当は書いてる最中にわかればいいのに…そんな時に補完機能とその型の表示機能が役に立ちます!(ここではVisualStudioCodeを使用しています)

f:id:kota-inamori:20170221185606g:plain

すぐさまUser型であることがわかりました!その他にも、補完するときにそのドキュメントも表示されていることがわかるとおもいます。もちろん型を明示するのはプログラマの責任ですが、コンパイル時だけでなくコーディング時にもその恩恵を受けることができます。

まとめ

今回はTypescriptとImmutable.jsの組み合わせについてご紹介しました。型付けによる補完機能の強化はTypescript自体の特徴の一つではありますが、データ構造の操作という中身がわかりづらい操作をする場合だと、その恩恵が顕著に得られることがわかります。

Alpine Linux 上でPHPのiconvがうまく動かない件の解決策

このブログでは何度もDocker関連の記事をあげていますが、今回もDockerの記事です。

弊社では Alpine Linux をベースイメージに使った公式のPHPイメージを使ってコンテナを作っているのですが、iconvという文字コードを変換する関数がうまく動かずに悩んでいました。

こんなエラーが出てました。

Wrong charset, conversion from `ISO-2022-JP' to `UTF-8//TRANSLIT//IGNORE' is not allowed.

bugs.alpinelinux.org

実際にこれはバグではなく、単にデフォルトで入っているiconvの実装が普通のものと違うことから生じているエラーなようです。

解決策

https://forum.alpinelinux.org/forum/installation/php-iconv-issue で紹介されている先のDockerfileを参考に最新のiconvのソースをビルドして置き換えることで解決できました。現在の最新のバージョンは1.15なのでDockerfileに以下のコードを加えます。

RUN curl -SL http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz | tar -xz -C ~/ &&\
    rm /usr/bin/iconv &&\
    mv ~/libiconv-1.15 ~/libiconv && \
    ~/libiconv/configure --prefix=/usr/bin && \
    make && make install

ENV LD_PRELOAD /usr/bin/lib/preloadable_libiconv.so

これだけで、PHPは新しく入れた方のiconvを使ってくれるようになりました!

ちなみに古い記事だとiconvのバージョンが1.14の方を使っているものがありますが、新しい Alpine Linuxgccではgets関数がなくなっているのでビルドすることができません。

DockerでPHPのPeclのextensionを入れる方法

今回はDockerの公式PHPイメージでPeclを使ったextension(mailparse)を入れるのに苦労した話をします。

Pecl extensionsの入れ方

公式Dockerイメージを使う場合は、extensionを有効化するためにdocker-php-ext-*という名前のコマンド群が用意されています。

PHP Official Image

PHPにもともと入っているextensionsはDockerfile内で

docker-php-ext-install curl

とすれば入ります。

Peclの場合でもかなり簡単で、memcachedを入れようと思ったら

pecl install memcached
docker-php-ext-enable memcached

で有効にできます。

コンパイルオプションの必要なものはこのままではできない

今回はmailparseというextensionを入れようとしたのですが、ドキュメントを見ると--enable-mailparseというPHPコンパイルオプションが必要だと書いてあったので、上の方法だけでは入りません。

途方に暮れて、公式イメージのDockerfileを眺めていると$PHP_EXTRA_CONFIGURE_ARGSという変数が使われているのを発見しました。

github.com

ここに値を入れれることでオプションを追加することができます。

dockerはバージョン1.9頃から--build-argというコマンドラインオプションを提供しています。

qiita.com

ということでDockerfileには

pecl install mailparse
docker-php-ext-enable mailparse

と書いておいて、build時に

docker build . --build-arg PHP_EXTRA_CONFIGURE_ARGS="--enable-mailparse"

とすることで無事mailparseが使えるようになりました!

Googleが昔採用していたバグ予測アルゴリズムをやってみた

今回はGoogleが昔採用していたバグ予測アルゴリズムのもとになっているFixCacheについて書きたいと思います。

バグ予測アルゴリズム

かなり前の記事ですが、こんなものがありました。

www.publickey1.jp

元の記事はこちらです。

google-engtools.blogspot.jp

巨大なコードを一つのリポジトリで管理しているGoogleはバグ予測アルゴリズムによって、レビュー時にどのファイルをより注意深く見るべきかの指標を出していたようです。

アルゴリズムの説明

FixCacheというアルゴリズムについての説明はこの論文に書かれています。

執筆者は UC Davis の方のようです。

ではこのアルゴリズムの説明に入る前にキャッシュについて理解する必要があります。CPUはメモリへのアクセス時にキャッシュを使うことでI/O時間を削減することでより効率的に動作するようになっています。ここで理解するべきキャッシュの特性は、

  1. temporal locality
  2. spatial locality

の二つです。この二つの説明は僕のブログでしていますが、簡単にいうと

  1. 一度アクセスされたデータは近いうちにアクセスされる可能性が高い
  2. 一度アクセスされたデータの周辺にあるデータはアクセスされる可能性が高い

という意味です。

takuseno.hatenablog.com

本題

ではFixCacheのアルゴリズムはどうなっているかというと、バグの発生とこのキャッシュの仕組みになぞらえて考えることができるというものです。先ほどの特性をバグに当てはめて見ると。

  1. 一度バグを修正されたファイルは再び修正される可能性が高い
  2. バグが発見されたファイルと一緒によく編集されるファイルはバグを含んでいる可能性が高い

というふうに言えます。さらに論文ではもう一つの churn locality というものを加えています。これは

  • 最近編集されたファイルはバグがあるかもしれない

という感じです。おそらくこのポリシーはLRU(Least Recent Used)というコンピューターサイエンス用語で、最も最近使われていないものから交換するというキャッシュの更新法のことだと思います。

この3つのポリシーに従ってコミット履歴を見ていくことでバグの予測ができるというわけです。

実際にやって見た結果

コードがどこかにいってしまって載せられないのですが、弊社の古くからあるプロジェクトでコミット修正した基準を、コミットメッセージに修正またはfixという文字が入っているかどうかという基準でやって見たところヒットレートが50%でした。キャッシュに置くファイルの数を全ファイルの20%にしたら良いという記述があった気がするのでそれに従いました。

この結果は結構すごいいい感じかも?と思いましたが、原論文では完璧に評価するには

  1. バグ予測されたがバグはないファイル (FP)
  2. バグ予測されてバグのあるファイル (TP)
  3. バグ予測されてなくてバグのあるファイル (FN)
  4. バグ予測されてなくてバグのないファイル (TN)

を考えた時に精度は

Precision = TP / (TP + FP) = TP / InCache

というふうに考えられるという感じのこととか色々書いてありましたが、これは理論上のお話です。論文中でも実際の評価は普通のヒットレートを使用したようです。

実はもう使われていない

頑張って紹介して見ましたが、このアルゴリズムというよりもバグ予測ツール自体がもう使われていないようです。

このようなバグ予測ツールがエンジニアにとってあまり有益な結果を出さなかったみたいです。

詳しいことはリンク先の論文やサイトを見るとわかると思います。

GitHubのメンションをDMで教えてくれるボット

この記事はボット・クローラー Advent Calendar 2016の14日目です。

今回はGitHubのメンションをSlackのDMで教えてくれるボットを紹介します。

弊社でのボット

弊社ではHubotをHeroku上で動かしています。以前にもこのブログで紹介しました。

http://blog.matchingood.com/entry/2016/11/29/201114blog.matchingood.com

メンションをDMで教えてくれる機能は初期から用意していました。理由は弊社の開発フローでGitHubの存在が大きくなったのですぐにGitHub上での更新を知る必要が出てきたからです。

http://blog.matchingood.com/entry/2016/11/08/142906blog.matchingood.com

特にメンションはSlackでDMで伝えてくれると開発用のチャンネルの通知を出さないようにしていても通知してくれるのでとても便利であることは明らかでした。

コード

プルリクエストへのレビューコメントのhookに対するコードは簡単に書くと以下のような感じです。

users =
  'takuseno': 'seno'

module.exports = (robot) ->
  robot.router.post "/github/pr/review", (req, response) ->
    data = req.body
    action = data.action
    path = data.comment.path
    user = data.comment.user.login
    url = data.pull_request.html_url
    number = data.pull_request.number
    title = data.pull_request.title
    repo = data.pull_request.head.repo.name
    body = data.comment.body

    if action != 'created'
      response.end ''
      return

    attachment =
      content:
        fallback: body
        pretext: path
        mrkdwn_in: ["text", "pretext", "fields"]
        color: "#f1c40f"
        author_name: users[user]
        title: "\##{number} #{title}"
        title_link: url
        text: body

    mentions = body.match /@(\S+)\s*/
    if mentions?
      for index, name of mentions
        if users[name]?
          userId = users[name]
          robot.adapter.client.openDM userId, (data) ->
            attachment.channel = userId
            robot.emit "slack.attachment", attachment

  response.end ''

ポイントは上記のコードではusersGitHubのユーザー名とslackのユーザー名の対応表を入れておいて、コメントを正規表現でメンションを探してslackに投げているところです。

あとは、DMの見た目を良くするためにattachmentというのを使っています。slackのattachment自体は以下に載っています。

api.slack.com

実際の感じ

f:id:takuseno:20161213174233p:plain

これでGitHub上でのコミュニケーションがよりスムーズになりました!

Laravelでファイルをデータベースで管理するライブラリを公開した

今回はLaravel上で簡単にファイルをデータベースで管理できるようにするライブラリを公開したので紹介したいと思います。

laravel-eloquent-storage

github.com

このライブラリはEloquentモデルに

<?php

use MatchinGood\EloquentStorage\EloquentStorage;

class UserFile extends EloquentStorage
{

}

という感じで継承させるだけで、そのモデルでファイルを管理できるようになります。

仕組み

データベースにファイルを保存するのはデータベースの容量を圧迫するだけでなくパフォーマンスにも影響が出てきます。

なので、容量単位の単価がはるかに安いS3にファイルを保存するようにしました。このライブラリではこの辺りのファイル管理を隠蔽しています。

概念的には以下の図で表せます。 f:id:takuseno:20161206132835j:plain

S3にはファイル名の衝突を避けるためにユニークなファイル名で保存し、MySQLには保存したユニークなファイル名とオリジナルのファイル名を保存します。

これにより、ファイル名の衝突を避けつつダウンロード時にはオリジナルのファイル名を使うことができます。

実際に使うと

例えばユーザーがアップロードしたファイルをUserFileというモデルで保存しようと思った時は

<?php

$file = $request->file('file');
$userFile = new UserFile;
$userFile->saveFromUploadedFile($file);

たったこれだけでファイルを保存することができました。さらにそのファイルをダウンロードさせようと思ったらコントローラで、

<?php
$userFile = UserFile::find(1);

return response()->downloadEloquentStorage($userFile);

とするだけでダウンロードさせることができます。これはResponse Macroをサービスプロバイダーで定義することでdownloadEloquentStorageという関数を使うことができるようになっているからです。

laravel.com

開発環境時の工夫

システムを開発時にS3に繋げたくありません。そこでconfig/eloquentstorage.phpでドライバーを設定することができるようになっています。

<?php
return [
    // this is the same drivers as Storage facade on Laravel
    // ELoquentStorage uses Storage facade internally
    'driver' => 'local'
];

このライブラリは内部的にはStorageを使っているので、S3にあげたくない場合はドライバーをlocalにすることでローカル環境にファイルを保存します。

実際にデプロイするときにこのドライバーをs3とすることでS3に保存できるようになります。

まとめ

最近弊社では積極的にオープンソースのライブラリを公開してコミュニティに貢献しようという熱が高まってきました。このような軽量なものから、まだ公開していない大きなライブラリまであります。このような環境はエンジニアにとってはとても魅力的な環境であると考えているからこそ実践させてもらっています。

PHPのGeneratorは本当にメモリ消費量が減るのか実験

modern phpという書籍の勉強の過程で、PHPのGenerator機能の実験をしました。 https://www.amazon.co.jp/Modern-PHP-Features-Good-Practices/dp/1491905018www.amazon.co.jp

Generator機能を使うことで、 大量のデータを繰り返しで処理するときに、 使用メモリ量が少なくなるということなのですが、 実際のコードを見てみましょう。

たとえば、1番から100,000番までの番号を、 順番にエコーする以下のコードがあるとします。

<?php
function makeNumbers() {
    $numbers = array();
    for ( $index = 0; $index <= 100000; $index++ ) {
        $numbers[] = $index;
    }
    return $numbers;
}

$startTime = microtime(true);
$startMemory = memory_get_usage();
foreach ( makeNumbers() as $number ) {
    echo $number;
}
$executionTime = microtime(true) - $startTime;
$usedMemory = (memory_get_peak_usage() - $startMemory) / (1024 * 1024);

echo "実行時間:{$executionTime}s";
echo "使用メモリ量:{$usedMemory}MB";

$numbersという配列に大量のデータが入るので、 メモリをたくさん使ってしまいますね。 Generatorを使えば、以下のように書けます。

<?php
function makeNumbers() {
    for ( $index = 0; $index <= 100000; $index++ ) {
        yield $index;
    }
}

$startTime = microtime(true);
$startMemory = memory_get_usage();
foreach ( makeNumbers() as $number ) {
    echo $number;
}
$executionTime = microtime(true) - $startTime;
$usedMemory = (memory_get_peak_usage() - $startMemory) / (1024 * 1024);

echo "実行時間:{$executionTime}s";
echo "使用メモリ量:{$usedMemory}MB";                             

yieldを使うことで、このように実装できます。 大量のデータが入っている配列の$numbersがなくなりましたね。

本当にメモリ消費量が減ったのでしょうか? 以下のような結果になりました。

種類 メモリ消費量[MB] 実行時間[s]
ジェネレータなし 13.972MB 0.286s
ジェネレータあり 0.004MB 0.401s

メモリ使用量が激減しました! 実行時間が増えているのが気になるところです。

当社ではGeneratorが使えるPHP7で開発をしています。