読者です 読者をやめる 読者になる 読者になる

Capistrano 2で複数のログを tail するときに見やすくする

cap log するとデプロイ時に観察したいログがとにかく tail できるということになっている。

しかしまとめて流れてくるので、どのホストからなのかなどわからなくて少し困る。ので、出力にホスト名とファイル名を含めるようにした。

出力の例:

[xxx.xxx.xxx.xxx (/service/My/log/main/current)]: 2016-02-23 16:29:02.292284500 Use of uninitialized value in string ne at lib/My/Service/Post.pm line 178.
[xxx.xxx.xxx.xxx (/service/My/log/main/current)]: 2016-02-23 16:29:02.292285500 Use of uninitialized value in string ne at lib/My/Service/Post.pm line 178.
[xxx.xxx.xxx.xxx (/var/log/app/workermanager.log)]: 2016-02-23T16:29:51 TheSchwartz TheSchwartz::work_once got job of class 'My::Worker::SomeDelayedTask', priority 80
[xxx.xxx.xxx.xxx (/var/log/app/workermanager.log)]: 2016-02-23T16:29:51 TheSchwartz Working on My::Worker::SomeDelayedTask ...
[xxx.xxx.xxx.xxx (/var/log/app/workermanager.log)]: 2016-02-23T16:29:53 TheSchwartz TheSchwartz::work_once got job of class 'My::Worker::SomeDelayedTask', priority 80
[xxx.xxx.xxx.xxx (/var/log/app/workermanager.log)]: 2016-02-23T16:29:53 TheSchwartz Working on My::Worker::SomeDelayedTask ...
[xxx.xxx.xxx.xxx (/var/log/app/workermanager.log)]: 2016-02-23T16:29:54 TheSchwartz job completed My::Worker::SomeDelayedTask process:2401 delay:1968
[xxx.xxx.xxx.xxx (/service/My/log/main/current)]: 2016-02-23 16:29:55.907968500 Use of uninitialized value in string ne at lib/My/Service/Post.pm line 178.
[xxx.xxx.xxx.xxx (/service/My/log/main/current)]: 2016-02-23 16:29:55.907969500 Use of uninitialized value in string ne at lib/My/Service/Post.pm line 178.

Cap の run メソッドはコールバックを受け取ることができて、コールバックの引数に SSH のコネクションをあらわすオブジェクトが渡ってくる。

このオブジェクトから IP をもらってホスト上の出力を素朴に puts するだけ。

ホストの role とか出せるとよかったけど、手間そうだったのでログのファイル名を出せばわかるだろうということでこれくらいにした。

近況

フォローしている人がスターをつけたリポジトリを詳細に見たい

https://embed.gyazo.com/fa897cc8ceb934f3edf66cee4d7cd839.png

https://github.com/https://github.com/USER.private.atom?token=XXX で見れるタイムラインを見て他の人が star をつけたリポジトリをチェックするといったことをしているとき、どういうリポジトリなのか・言語はなにかなどを知りたいと思うことがしばしばあったので、いいかんじに表示する Web アプリケーションを書いた。

素朴に GitHubAPI へリクエストを投げたらだいたい 1000msec * 25-30 くらいでレスポンスが返ってくるまで30秒くらいかかってさすがに耐え難いので並列化した。

しかし Thread を扱うにも同時並列数の制御がむずかしいので諦めて parallel という gem を使った。

いまは単に HTML を返すだけだけど Atom を吐くようにして Feedly あたりにつっこみたい。

あと Feeds APIBASIC (パスワード) 認証じゃないと private なフィードの URL を返してくれなかった。

Scala (再) 入門

思うところあって Scala に (再) 入門した。

(再) というのも、だいぶ前にコップ本を読んだきりでけっこういろいろ忘れていた + そのとき知らなかった概念がけっこう多かったので、ほぼ一からという雰囲気。

LTSV のパーサひとつを書くのに時間がかかったのはずっと implicit conversion の仕様を調べていたからだと思う。

Ruby でメソッド内で定数を定義する方法

Ruby ではメソッド内で定数を定義することは通常できない。

例:

def k
  K = 1
end

# /Users/aereal/Dropbox/sketches/const.rb:2: dynamic constant assignment
#   K = 1
#      ^

ちなみに dynamic constant assignment は「メソッドの定義内で定数を定義できない」という意味でしかなく、以下の定義はすべて有効であり警告も出力されない ( Ruby 2.2.4 )。

if rand(10).floor.even?
  N = 0
else
  N = 1
end

M = $M

if rand(10).floor.even?
  K = 1
end
# 定数 `K` を参照すると 1/2 の確率で NameError 例外が発生する。

定数なので静的な文脈で定義すればよいのだけれども、既に定義済みの定数を動的に書き換えたいことがある。たとえば OpenSSL::SSL::VERIFY_PEER など……。

さてメソッド内で定数を定義する方法はいくつか考えられる。

define_method を使う

self.class.send(:define_method, :k) { K = 1 }

Kernel#.require で定数を定義する別ファイルを読み込む

const.rb:

K = 1

k.rb:

def k
  require 'const'
  K
end

おわり

メタプログラミングすればだいたいなんとかなる Ruby のことなので、だいたいメタプログラミングなどでなんとかなった。

堅牢さが欲しいと思いつつも、いざとなればメタプログラミングでこじあけられるということは、ブラックボックスである部分が少なくなるということであり、いろいろなライブラリを組み合わせてアプリケーションを書くときにはけっこう助かることも多いですね。

2015 → 2016

2015

2015-01-01から1年間の記事一覧 - Sexually Knowing

「安全にソフトウェアをつくる」ことが大きな関心であり、そのために CI (Jenkins) をよりディープに使おうとしたり、Ansible や Homebrew を活用していわゆる Configuration Management を進めた。

そのおかげで Ansible はかなり手に馴染んできた。実際に仕事にも活かせる場面が出てくるほどに。

APISchema

APISchema を使っているのでいくらか P-R を送った

参考: DSLでAPIを書きたい!!APISchemaでらくらくAPI生活をはじめよう - hitode909の日記

YAPC

#yapcasia で『世界展開する大規模ウェブサービスのデプロイを支える技術』という発表をした - Sexually Knowing

また今年の YAPC::Asia 2015 Tokyo で2度目のトークの機会をいただけた。

世界展開する大規模ウェブサービスのデプロイを支える技術 / YAPC::Asia Tokyo 2015 // Speaker Deck

このトークで紹介した tarball を配るデプロイは他のチームでも一部導入を進めつつあり、そのうちいくらかコミットしている。

ごく個人的な感想として、こうしたカンファレンスでの発表をきっかけに開発を始めたりあるいは進めたり (= 発表ドリブン) することが多かったけれども、今回はまずある程度の形をなした成果があって、それをベースにして発表を組み立てる、という経験は初めてのことだった。

おかげで発表の内容を深めたり、そのために必要なベンチマークをとったりするなど充実した準備をした上で発表に臨むことができた。

(もっとも、いままでがリラックスしすぎていた、ということはあるかもしれない)

DDD

輪講で『実践ドメイン駆動設計』を読むことになって、『エリック・エヴァンスドメイン駆動設計』を復習しつつ、DDD を実際に導入していくにはどうしたらいいんだろうね、ということを議論した。

輪講の場で実際のサービスの境界づけられたコンテキストやユビキタス言語を見出したり、実はあのシステムはここでいうイベントソーシングだったんだといった発見が得られたり、非常に実践的な知識が培われたと思う。

特に、実際のサービスのドメインモデルやアーキテクチャを再訪し省みるという試みはまさしく「実践」であり、我々が作ろうとしているものに DDD (あるいは別のやりかた) が適しているだろうか、適しているとしたらなぜなのか、といった深い検討を進められた。

犠牲的アーキテクチャのようなソフトウェアに寿命があるものと考えるある種の悲観的な見方もあるが、寿命を迎えたソフトウェアをただ塵に還すのか、なにか洞察を持ち帰るのかは、こういった試みの有る無しによるのだと思う。

参考: はてな社内で開催したDDD勉強会の様子をご紹介します - Hatena Developer Blog

実践ドメイン駆動設計 (Object Oriented SELECTION)

実践ドメイン駆動設計 (Object Oriented SELECTION)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

Haskell

『すごい Haskell 楽しく学ぼう!』を買って読んだ。断片的な知識しか身に付いていなかった Haskell について、ざっくばらんでもまとまった基礎的な知識を得られた。

Functor, Applicative Functor, Monad, Monoid, その他波及して、MonadPlus, Semigroup などについて (不完全あるいは不正確かもしれないが) ひととおり基礎的な背景知識が得られた。

新しいプログラミング言語を学ぶとき、新しい考え方を得て他の言語と比較し相対化するということに最も価値を感じており、たとえば Haskell ではいまのところ型クラスと Functor や Monad といった素朴ながら強力な抽象化のツールキットたちが大きな収穫となっている。

Haskell の型クラスは静的型付けと実装に対して開かれた抽象を実現する仕組みでとてもおもしろいし強力さを既に感じている。

自分は Ruby がけっこう好きで、その最も大きな理由としてオープンクラスを挙げるだろう。それと同等かそれ以上に強力な仕組みをコンパイル時に型検査が行われるという堅牢性と両立できているのは、よく考えられているなあ、と感動する。

Functor や Monad といったある種の計算に共通する属性を抽象化する型クラスたちに対する背景知識を得て、なるほどたしかに強力だと感じた。 現実の複雑な問題を解決するのに十分な力が、素朴なモデルに備わっているのはかっこいい。

おもしろかったので TypeScript の Generics や Abstract class の勉強がてら Maybe や State などの Monad を実装してみたり、Perl の Data::Monad というモジュールに Data::Monad::Either の実装を追加したりなど、それなりに実りもあった。

それに伴って Haskell の base パッケージscalaz のコードを読んだ。 scalaz のコードリーディングは楽しくて、パターンマッチングのような言語の子細な仕様の違いから、型クラスの実現方法の違いなど、大小様々な差異が見取れて Scala の implicit conversion まわりについても少し理解が進んだ。

また、『すごい Haskell』本を読んで高まり、もっと足元を固めたいと思ってウィッシュリストに入れた『やさしく学べる離散数学』を hitode909 さんから贈っていただいて練習問題をやりつつ読み終えた。

やさしく学べる離散数学

やさしく学べる離散数学

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

TypeScript, ES2015

変化の激しいフロントエンド界隈において落ち着きを見せはじめた ES2015 や TypeScript に触れはじめておもちゃを作ってみたり 本番投入を見据えたサービスで TypeScript を使いはじめたり、NPM モジュールを書いて公開するなどした。

あと redux と Electron のチュートリアルをやった。

2016

2015年はインプットが進んだ一年だった。次の1年、あるいはその先をどうするか。おおまかには:

  • 基礎的な知識の習得
    • 数学
    • 計算機科学
  • UNIX 哲学を体現したソフトウェア的アウトプット

……を考えている。

基礎的な知識の習得

高等教育をろくに受けておらず、特に数学や計算機科学はかなり怪しい。

とはいえ、そういった問題意識がありつつも、いまはシンプルにここらへんについてもっと知ることができたら楽しくなりそうだな、という好奇心のほうが強い。

おもしろそうなことを知りたい、という以上の理由はいらなさそうだし、結果的に仕事や趣味に役立ったらお得だな、という気持ち。

UNIX 哲学を体現したソフトウェア的アウトプット

  • UNIX 哲学の体現
  • ソフトウェア
  • アウトプット

……という3つの小テーマがある。

UNIX 哲学の体現とは、『すごい Haskell』本で学んだ Monad など現実の問題に対処できる力を持った強力ながらシンプルなモデルが組み合わされて複雑な問題に立ち向かうことができればきっとうまくいくだろう、ということ。 つまり巨人 *1 の肩に乗るのが一番だということ。

アウトプットは文字通りで、1年間インプットしたわりに仕事のドメインに閉じがちなアウトプットだったので、もう少しオープンなものにしたい。 UNIX 哲学の体現とも関わりがあって、つまりいくつかのモジュールをプラガブルに組み立てて問題を解決したならばドメインロジックではない部分を分けられるはず。

ソフトウェア的としたのは、カンファレンスにおける発表を目標に据えるとしても、まずソフトウェアエンジニアとして問題を解決した・するという成果がなければだめで、自分にとって手近かつイメージしやすい成果とはつまりソフトウェア、それも OSS をつくることだろう、というところによる。

具体的なイメージとしては @substack 氏かもしれない。

*1:数学、あるいは UNIX

CI で実行するスクリプトをリポジトリに含めるときに古いブランチでも実行できるようにする

CI で実行すべき処理をシェルスクリプトなどにしてリポジトリに含めておく、というのはまっとうな管理というかんじで望ましい。

とはいえ、新しく CI のジョブを追加するなどして新しいスクリプトリポジトリに追加した場合、追加するコミットが含まれていない古いブランチの扱いをどうするか、という悩みは尽きない。

作戦としては:

  • 古いブランチでは実行しない
  • ファイルが (ブランチにおいて) 存在すれば実行するし、なければべた書きしたシェルスクリプトを実行する

……という2つが考えられて、いままでは後者をとってきた。

しかし、メンテナンスすべきところが2つあると億劫だし、たまに修正が漏れたりする。

そこで、次のようなやりかたをとってみる:

  • ブランチにおいて存在したらそれを実行する
    • (これまで通り)
  • 存在しなければ、スクリプトが必ず存在するブランチから git-cat-file で内容を出力してパイプして実行する

「必ず存在するブランチ」とは、master など統合ブランチでもよいし、あるいはトピックブランチでもよい。
SHA1 で特定のコミットを指してもいいけど、リポジトリでバージョン管理したいという旨からするとブランチのほうが望ましいように思う。

具体的には以下のようなかんじ。

CI で実行したいスクリプト:

#!/bin/bash

set -e -u

carton install
carton exec -- prove t/

CI の設定 (シェルスクリプト):

ci_script=script/ci/run
if [[ -f $ci_script ]]; then
  bash $ci_script
else
  git cat-file blob origin/add-ci-script:$ci_script | bash -
fi

こうしておくと CI の設定は最小限度で済むし、基本的にそのブランチごとの最新のスクリプトが実行されるのでバージョン管理の恩恵が受けられる。
あと、細かい点ではあるけれどもパイプして実行しているのでごみを残さないのもよい (( ファイルを残しておくとうっかり同名のファイルをリポジトリに追加すると git-checkout に失敗してしまう ))。

iTerm を全画面で表示するのをやめて画面の左半分に表示するようにした

Cmd-Return で全画面にできるのでそのようにしていたけれども、実際のところターミナルが全画面で表示できていて嬉しいことってあまりなかったので左半分に出すようにした。

f:id:aereal:20151221145104p:plain

上半分ではなく左半分にしているのは、ターミナルで見たい情報は幅よりも高さがあることが多いから *1

*1:ログなど

連打を支える技術

この記事は、はてなデベロッパーアドベントカレンダーの19日目の記事です。

昨日は、id:t_kyt による あれから一年、あの TypeScript プロジェクトは今 - 多幸感 でした。


すばやく、かつ堅牢にアプリケーションをつくる

はてなid:aereal です。アプリケーションエンジニアとして日々、サービスの開発に携わっています。

はてなではサービス開発合宿が年に一度ほどのペースで開催されています *1。今年もつい先日開催されました。

私たちのチームは技術的な挑戦を行う一方で、プロトタイプではなく初期実装となるプロダクトを作り上げることを目的として開発に取り組みました。

技術的挑戦について具体的にあげると、大きな視点ではサーバサイドを Go, クライアントサイドを TypeScript で書くことです。それも2日半で初期実装たりうるクオリティを最低限担保してのことです。

Go や TypeScript は既にプロダクションで採用されていますが、はてなにおけるチームすべてに浸透しているわけではありません。 実際、Go や TypeScript でプロダクションのコードを書いた経験がないメンバーが私たちのチームにもいました。

その他にもミドルウェアの選定やアーキテクチャの採用において、勢いを保ちつつもプロダクションへの投入を見据えてチーム全員で検討を行いました。

この記事では、今年の開発合宿で取り組んだ Web アプリケーションにおいて、ボタンの連打を効率よくかつ体験を損うことなく実現するために工夫した点について紹介します。

(なお、以下で紹介する Web アプリケーションは開発中のものであり、一般に公開されておりません。公開の予定も未定であることをあしからずご了承ください。)

ボタンを連打したくなる性と向き合う

私たちが取り組んだサービスでは、ページ上にボタンが存在します。クリックするとページに対して簡単なフィードバックのようなものがつけられます。 つけたフィードバックをあとから消すこともできます。 ちょうどはてなスターのようなものです。

ボタンがあれば連打したくなりませんか? *2

アプリケーションを作り提供する側としては、しばしばそのような業にも似た性は深刻な問題になりえます。

素朴に実装すると次のようなコードが考えられます:

function createFeedback(event) {
  return $.ajax({
    // ...
  });
}

const buttons = document.querySelectorAll('button[data-add]');
Array.from(buttons).forEach((button) => {
  button.addEventListener('click', createFeedback);
});

これではあまりに素朴すぎ、いくつかの問題があります。

特に大きなものとして、クリックごとに HTTP リクエストが発生する、という問題は無視できません。

この課題に対して実際にどのような対策を行ったのか、サーバサイドとクライアントサイドについてそれぞれ紹介します。

サーバサイドにおける工夫

イベントソーシング

連打という文脈から少しずれますが、まずアーキテクチャにおける検討を紹介します。

アプリケーションのアーキテクチャを検討する際、「あるページにつけられたフィードバックのようなもの」をどのように保存するかをまず検討しました。

ユーザ (主体) * ページ (対象) * クリック (行動) というオーダーになるため、注意深く設計しないとレコード数が爆発したり、パフォーマンスを保つことがむずかしくなります。

そこで私たちは Event Sourcing と呼ばれるアーキテクチャをとることにしました。

詳細としては、ボタンをクリックすると HTTP リクエストが発生し、「フィードバックをつけた」というイベント *3 を表現するリソースをサーバへ送ります。 削除する際は、「このフィードバックを削除する」というイベントを表現するリソースをサーバへ送ります。

サーバでは、送られてきた「フィードバックをつけた」「フィードバックを消した」というイベントを表現するリソースを一旦、そのまま保存します。 サーバから表示すべきフィードバックの情報を返す際は、「つけた」「消した」というイベントを集めて計算した値を返します。

加えて、ある時点におけるスナップショットを保存することで計算するときにも効率化を図っています。この時、スナップショットをとった時点より古いイベントのレコードを削除することで、レコード数の増加を緩やかに保つことができます。

こうしたアーキテクチャにより効率よくデータを保存しつつ、データの書き込み時に担保すべき一貫性の範囲を小さく抑えられます。またパフォーマンスの低下も抑えることが期待できます。

リクエストに複数のリソースを含められるように

素朴な RESTful アーキテクチャでは、ひとつのリクエストでひとつのリソースを作るような実装がとられがちです。

例:

POST /users HTTP/1.1
Content-Type: application/json

{
  "name": "aereal"
}

これでは効率が悪いため、ひとつのリクエストに複数のリソースを含めて作成できるように実装しました:

POST /users HTTP/1.1
Content-Type: application/json

{
  "users": [
    { "name": "aereal" },
    { "name": "noreal" }
  ]
}

クライアントサイドにおける工夫

イベントのバッファリング

サーバサイドが複数のリソースを受け取れるようになっても、先にあげた click イベントのハンドラで都度 HTTP リクエストを送るような素朴な実装のままではリクエストあたりに含められるリソースは1つのままです。

そこで click イベントをバッファにためてまとめて送るように実装を変更しました:

function bulkCreateFeedback(events) {
  const resources = events.map(e => { /* some transform */ });
  return $.ajax({
    data: { resources: resources },
    // ...
  });
}

const buttons = document.querySelectorAll('button[data-add]');
let buffered = [];

function onClick(event) {
  buffered.push(event);
}

Array.from(buttons).forEach((button) => {
  button.addEventListener('click', onClick);
});
setTimeout(() => {
  if (buffered.length > 0) {
    bulkCreateFeedback(buffered);
    buffered = [];
  }
}, 500);

これで HTTP リクエストは500ミリごとに高々1回まで抑えられます。

accumulate-call で簡潔に書く

バッファリングの詳細から離れて、アプリケーションコードを書く側から見ると「T[] を受け取る関数」を「T を受け取る関数」に変換している、といえます。

そこでこの素朴な関数の変換のみを行う accumulate-call という NPM モジュールを公開しました。

github.com

README から使い方を引用します:

import { accumulateUntil } from 'accumulate-call';

document.body.addEventListener('click', accumulateUntil((events) => {
    console.log(`Clicked ${events.length} times`);
}, 1000));

ずいぶん簡潔になりました。見た目は素朴にイベントハンドラを与えているのと大差ありません。

また accumulate-call は TypeScript で書かれています。 型定義ファイルを NPM パッケージに含めているため、TypeScript 1.6 以降を使用している場合は DefinitelyTyped などの外部のリポジトリを参照することなく accumulate-call の型定義を tsc に伝えることができます。

参考: Typings for npm packages · Microsoft/TypeScript Wiki · GitHub

むすび

はてなでは、レイヤを横断して体験をよりよくする気概のあるエンジニアを募集しています。

(なお応募フォームは連打せずに1回だけ送信ボタンを押してください)

hatenacorp.jp

明日は id:hakobe932 です。

*1: サービス開発合宿 - Hatena Developer Blog

*2:私は連打したくなります。

*3: 特にことわりがない限り、「イベント」は DOM Event を、「リソース」は REST アーキテクチャスタイルにおける文脈のリソースを指すものとします。

JSON::TypeInference 1.0 と JSON::Schema::Generator (trial) をリリースした

JSON::TypeInference 1.0

metacpan.org

だいたい欲しいものは実装したので覚悟を決めるという意味での 1.0 リリース。

これはなにか?

Perl の値 (スカラ値、ハッシュリファレンス、配列リファレンスなど) が JSON のどの型であるかを推論するモジュール。

Perl の値から JSON の型を推論するモジュールを書いた - Sexually Knowing

できること

後述の JSON::Schema::Generator のような使い方を念頭に置いてある、というか、そのために作った。

JSON::Schema::Generator

metacpan.org

与えられたデータから型を推論し、構造を再帰的に辿りながら、JSON Schema の雛形を生成する。

こういう感じでデータを与えると:

my $generator = JSON::Schema::Generator->new;
$generator->learn({
  id   => 1,
  name => 'yuno',
});
$generator->learn({
  id   => 2,
  name => 'miyako',
});
$generator->learn({
  id          => 3,
  name        => 'sae',
  accessories => ['eyeglass'],
});
my $schema = $generator->generate;

こういう雛形が得られる:

{
   "$schema" : "http://json-schema.org/draft-04/schema#",
   "description" : "TODO",
   "properties" : {
      "accessories" : {
         "items" : {
            "example" : "eyeglass",
            "type" : "string"
         },
         "type" : "array"
      },
      "id" : {
         "example" : 1,
         "type" : "number"
      },
      "name" : {
         "example" : "yuno",
         "type" : "string"
      }
   },
   "required" : [
      "id",
      "name"
   ],
   "title" : "TODO",
   "type" : "object"
}

array だったら items.example に代表値を置くとか、けっこうおもてなしできていると思う。

null かもしれない値を "type": ["null", "string"] のようにするのがいいのか、required から外すだけでいいのか、もうちょっとユースケースを見極めたい。

JSON::TypeInference 0.04 で null かもしれない値を Maybe 型として推論するようにした

metacpan.org

github.com

Null と値型のただ2つからなる Union 型を Maybe という特別な型として推論するようにした。ちょっと便利。

Maybe だったら {"type" : ["null", "string"]} のように出力するようにすると、より厳密な JSON スキーマを生成することができるようになった。

github.com

ほか JSON::TypeInference::Type::* にドキュメントを追加するなどしました。

どうぞご利用ください。

最近の CPAN モジュールを作るときの構成

github.com

Minilla を使っている。minil new してからちょっと手を入れる。

.mailmap

<aereal@aereal.org> <aereal@users.noreply.github.com>

GitHub の Web インターフェースで Merge Pull Request ボタンを押すとコミットの AUTHOR_EMAIL がこれになる。

たぶんメールアドレスを非公開にしているとこれになるような気がする。悩ましい。

.travis.yml

sudo: false # Use container-based environment
language: perl
perl:
  - "5.12"
  - "5.14"
  - "5.16"
  - "5.18"
  - "5.20"
cache:
  directories:
    - local # Caches installed modules

before_install:
  - which carton || cpanm --notest Carton
  - carton version

install:
  - carton install

script:
  - make -f ci.mk cover

after_success:
  - make -f ci.mk coveralls

……をやっている。

ci.mk

テストで実行するコマンドを Makefile にまとめる。

CARTON_EXEC = carton exec --
COVER_IGNORE_RE = ^t/|^local/

test:
	$(CARTON_EXEC) prove t/

cover:
	$(CARTON_EXEC) cover -test -ignore_re '$(COVER_IGNORE_RE)' -make 'make -f ci.mk test'

coveralls: cover
	$(CARTON_EXEC) cover -report coveralls

感想

Dist::Maker を使ってみようかとおもったけど依存モジュールが多くてちょっとためらう。

Perl を書きはじめて4年くらいになるけど、小さな CPAN モジュールをリリースするようになってからなんとなく Perl を書くのが好きになってきた気がする。

Perl の値から JSON の型を推論するモジュールを書いた

github.com
metacpan.org

これは何か

Perl の値 (スカラ値、ハッシュリファレンス、配列リファレンスなど) が JSON のどの型であるかを推論するモジュール。

Perl1 という値は JSON の number である、Perl"a"JSON の string である、という風な。

配列やハッシュのようなコンテナ型も再帰的に推論する。[1,2,3] のような値を与えると array[number] という型である、と推論する。

候補となる型が複数ある場合は直和型 (union type) として報告する。すなわち [1, 'a'] のような値は array[number|string] と推論する。

何のために使う?

もともとこのモジュールは JSON schema を自動生成することを目指して作った。

JSON schema は JSON over HTTP API を提供するときに便利な仕様群であるものの、JSON schema それ自体を記述するのはリーズナブルとは言い難い。
これはソフトウェアが再利用することを意図しているので仕方のないことではある。とはいえ書くのはだるい。
ずっと { "type": "object", "properties": {} } と書いていると気が滅入る。

JSON schema を実際の JSON データを (複数) 与えて自動的に型や構造を出力してくれると便利そうである。

実際、そのようなことをしてくれるライブラリはある。
(参考: JSON Schema Software)

しかしながら当然 (?) Perl のライブラリは紹介されていない。CPAN でよさそうなそれらしいモジュールは見つけられなかった。なので書くことにした。
このモジュールはその一歩です。

実際に JSON schema を自動生成するデモ

github.com

JSON::TypeInference を使って実際に与えられたデータから schema を自動生成するスクリプトを書いてみた。

README にあるように eg/ 以下の JSON からこんな schema が生成される:

{
   "$schema" : "http://json-schema.org/draft-04/schema#",
   "description" : "TODO",
   "properties" : {
      "id" : {
         "example" : 1,
         "type" : "number"
      },
      "name" : {
         "example" : "yuno",
         "type" : "string"
      },
      "school" : {
         "properties" : {
            "location" : {
               "properties" : {
                  "lat" : {
                     "example" : 14,
                     "type" : "number"
                  },
                  "lon" : {
                     "example" : 15,
                     "type" : "number"
                  }
               },
               "type" : "object"
            },
            "name" : {
               "example" : "yamabuki",
               "type" : "null"
            }
         },
         "type" : "object"
      }
   },
   "title" : "TODO",
   "type" : "object"
}

union type の扱いが雑なのは将来的に直したい。

課題と展望

あるオブジェクトのプロパティが必ず出現すべきなのかどうか、も推論できるとよさそうと思って issue を立ててある。
Detect required properties · Issue #5 · aereal/JSON-TypeInference · GitHub

感想

型推論などというのもおこがましいほど素朴な実装だけど、しゅっと書けたわりには便利なものができたと思う。

どうぞご利用ください。

Config::ENV で各環境で必ず定義すべきキーを定めておく

metacpan.org

ちょうどいいデフォルト値が見当たらないので、このキーは各環境で適宜、定義せよ、という風にしたい。interface みたいなかんじ。

use strict;
use warnings;
use Test::More tests => 2;
use Test::Fatal qw(exception);

{
  package My::Config::NotImplemented;

  use strict;
  use warnings;
  use Scalar::Util qw(blessed);

  sub new {
    my ($class) = @_;
    return bless {}, $class;
  }

  sub equals {
    my ($self, $other) = @_;
    return blessed($other) && $other->isa(ref($self));
  }
};

{
  package My::Config;
  use strict;
  use warnings;
  use Config::ENV 'MY_ENV';

  use constant NOT_IMPLEMENTED => My::Config::NotImplemented->new;

  sub declare ($) {
    my ($name) = @_;
    return ($name => NOT_IMPLEMENTED);
  }

  common +{
    declare 'log.path',
  };

  config production => {
      'log.path' => '/var/log/app.log',
  };

  config test => +{};
};

{
  package Test::My::Config;
  use strict;
  use warnings;
  use Config::ENV ();

  sub import {
    my ($class) = @_;
    no strict 'refs';
    no warnings 'once';
    *My::Config::param = \&param;
  }

  sub param {
    my ($class, $name) = @_;
    my $value = Config::ENV::param($class, $name);
    return !My::Config::NOT_IMPLEMENTED->equals($value);
  }
};

Test::My::Config->import;

my $envs = do {
  my $envs = My::Config->_data->{envs};
  [ keys %$envs ];
};

for my $env (@$envs) {
  local $ENV{MY_ENV} = $env;
  my $current = My::Config->current;
  for my $key (keys %$current) {
    ok +My::Config->param($key), "Environment ($env) defines `$key'";
  }
}

declare($name) で各環境で定義すべきキーを宣言する。これは対応する値として意味のないオブジェクトを便宜上定義する (Config::ENV は設定値として hashref を期待するので)。

テスト時に Config::ENV を継承したクラスの param($name) を上書きして、値が NOT_IMPLEMENTED オブジェクトかどうかをアサートする、という風にする。

すると # Failed test 'Environment (test) defines `log.path'' という風にテストが失敗し、たしかに test で log.path が定義されていない、ということがわかる。

JSON schema とか XML とか使うともっとリッチな検証ができそうだけど、Config::ENV でも素朴にできるというコンセプト。

#yapcasia に参加した

トークしつつ、トークを聞いたりなどした。

1日目

いろいろ聞いたけど印象的だったトークについて。

yapcasia.org

kazuho さんの発表を初めて見た。声が高い。

僕は大学を中退していてアカデミアどころか学部の卒業論文がどういう雰囲気なのかよく知らないけれども、論文やベンチマークなど根拠となるデータが必ずあってそれを軸に話がなされるので、けっこう速いペースにも関わらずすんなり聞くことができた。

アセットコンパイルを行う動機はリクエスト数を減らすだけではないので必ずしも HTTP/2 によってアセットコンパイルという考え方が無用の長物になることはないだろうと思った *1。とはいえ、言及されたように考え方を変える必要はあるだろうと思う。

言ってしまえば高々60分に満たない時間で HTTP/2 について一本筋の通った理解が得られたのは、事前に断片的な知識が自分にあったことを差し引いてもすごいことだな、と思った。

また、発表の内容自体もさることながら、自分の中で「わかりやすい」「聞きやすい」プレゼンテーションの具体例がひとつできたのも尊い。

yapcasia.org

WebAudio と信号入門の話。

「今まで Web エンジニアはディスプレイという限られた光情報しか操ることしかできなかったが、WebAudio によって空気を振動させディスプレイを越えて影響を及ぼすことができる」という風なことをおっしゃっていた。

少しおどけた風だったような気がするけれども、実際、胸を打たれた。僕はわりと音楽が好きで音が出ると喜ぶので WebAudio が Web API に入るときはおもしろそうだ、と思ったけれどもそれ以上のことは考えられなかった。

音波とか音響に関する知識はわずかながらあったけれども、信号の話は不明だったのでとてもおもしろかった。とてもエンターテイナーだなあ、と感じた。

yapcasia.org

2日目

yapcasia.org

タイトルにある通り Go におけるプロファイリングおよび最適化の話で、それはそれでもちろん参考になったけれども、思いがけずライブコーディングを見ることができてとてもよかった。

ごく個人的な感想

2012年にチケットを買っていて行くつもりだったけれどもぎりぎりでやめて、別の人に譲った。2014年も参加しなかった。どちらの年もトークを応募しなかったから。

自分はエンジニアで普段からインターネットなどを通じて情報を得たり OSS コミュニティからソフトウェア資産を享受し (つつ、わずかながら還元し) ており、では YAPC のようなカンファレンスで自分が果たせる役目とはなんなのかと問うてみると、おもしろトークをすることだと思っている。

また、そうやってトークをするという形で還元することをイベントに参加する条件として課しているのは「お客様気分」を持たせないためでもある。YAPC は有料のイベントなのでなおさらである。

YAPC::Asia Tokyo にて2回スピーカーとして登壇できたことはとても褒まれ高いことだと思っている一方で、YAPC::Asia Tokyo に区切りがついたとしてもそういったコミュニティへの還元への考え方は変わることはないだろう。

おもしろトークのために技術を磨いていかねば。

みなさまお疲れさまでした。

*1: たとえば browserify などはブラウザにおける JS 実行環境においてモジュールシステムがないことに対するアプローチである

#yapcasia で『世界展開する大規模ウェブサービスのデプロイを支える技術』という発表をした

世界展開する大規模ウェブサービスのデプロイを支える技術 - YAPC::Asia Tokyo 2015

2年ぶり2度目の YAPC::Asia Tokyo でタイトルにある通り発表をしてきました。資料は近日公開予定です。

立ち見続出のようで満員御礼といった具合で非常に嬉しいしありがたいことです *1

懇親会などで「おもしろかった」と声をかけてもらえることが多くて非常に嬉しかったです。 「おもしろかった」「最高」と思ったらぜひ ベストトークに投票 してもらえると嬉しいです!!!

*1: もっとも立ち見されていた方は60分近いトークだったので大変だったでしょうが

YAPC::Asia Tokyo 2015 のトークスケジュールを集めたカレンダーを作った

https://www.google.com/calendar/ical/037qm3orvt1bkf5g2lsi1br0bg%40group.calendar.google.com/public/basic.ics

YAPC::Asia Tokyo 2015 も来週に控えて、どういうトークを見にいくか悩むこの頃なのでカレンダーをつくりました。

yapcasia.org

トークリストから素朴にスクレイピングして作ってあります。organizer にスピーカーの名前が入れられるとよかったのですが、メールアドレスが必須で手頃に入手できそうになかったので諦めてイベントのサマリに入れてあります。

Google Calendar などにインポートするなどして、どうぞご利用ください。