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

Scala めく春

scala Vim

春なので4年前くらいにコップ本の初版を読んで以来の Scala を書きます。

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

Vim

vim-scala を入れて構文ハイライトなどを手に入れます。

syntastic

syntastic は後述する vim-ensime が syntastic のコマンドを呼んでおり、インストールしていないとエラーが出るので導入しました。

(もともと syntastic を使っていたのですが、非同期実行ができなかったのでブロックされてしまい使うのをやめていました。)

せっかくなので lightline.vim で構文チェック結果を表示するようにしました:

ENSIME と vim-ensime

ENSIME brings Scala and Java IDE features to your favourite text editor

ENSIME とは IDE が提供するような定義元へのジャンプや型の表示などのリッチな機能を提供するツールです。

ENSIME は、ビルドツールと統合してプロジェクトの設定を書き出し (.ensime)、エディタが JVM のプロセスを起動し TCP (WebSocket) で通信するというアーキテクチャになっています。

ドキュメントに書いてあるようにいくつかのエディタいくつかのビルドツールがサポートされています。

vim-ensime

ensime-vim は、ENSIME という IDE が提供するような定義元へのジャンプや型の表示などのリッチな機能を提供するツールVim 用クライアントを提供するプラグイン

一時は開発が停滞しており使い物にならなかったそうですが、今はわりと活発に開発されているようでドキュメントに書いてあるとおりに導入したところちゃんと動いて使えています。

vim-ensime は Python インターフェースから WebSocket で ENSIME のプロセスと通信するという実装になっています。

websocket-client という pip パッケージを入れる必要があるので、OS XPythonsudo を使って入れたくないので Homebrew で入れた Python を使います。 ……が、手元に入れていた MacVim はシステムの Python にリンクしていたので python/dyn が使えるよう MacVim をビルドします。

brew unlinkapps
brew unlink macvim
brew tap macvim-dev/macvim
brew install --with-properly-linked-python2-python3 --HEAD macvim-dev/macvim/macvim
brew linkapps macvim-dev/macvim/macvim
✘╹◡╹✘ < vim --version | grep python
+cryptv          +linebreak       +python/dyn      +vreplace
+cscope          +lispindent      +python3/dyn     +wildignore

okok.

sbt

公式のインストール手順に書いてあるとおり sbt プラグインをインストールするだけ。

思い出す

手を動かします。

はてなインターン事前課題をやって、はてな教科書を読んで課題を進めます。Scala で Web アプリケーションを書くにあたってひととおりなにか書くにあたって、課題がまとまっていてよいです。

コップ本を片手に Scala API リファレンス を眺めるのも乙なものです。

おわり

(ひさしぶりに) Scala を書くにあたっていろいろ環境を整えたり、Scala を書く際の勘所を得る (取り戻す) ためにコードを書いたり資料を読んだりしました。

次は Play でちょっとしたアプリケーションを書いたので、その記録です。

便利 zsh 設定 N 連発

zsh

シェルなどの設定の知見が意外と広まってないよね、ということで話題になったので書きました。

:q で exit

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L174

Vim 使っていると :q したくなるので alias をつくった。

バックスラッシュをチルダに置き換える

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L109-L120

~/ はけっこう使うわりに押しにくいので、バックスラッシュを入力したら ~/ に展開するようにしてある。

カーソル位置の左が空白か行頭の場合のみ展開する、という風にしているので echo 'a\'' などは普通に入力できる。

Vim 風に tmux のペインを分割してコマンドを実行

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L153-L171

C-w v とか C-w s で、現在プロンプトに入力しているコマンドを分割したあとのペインで実行する。

tail -F /var/log/app/fluentd.log[C-w]v などと入力すると縦分割してログを眺められて便利。

エスケープが雑なのと、分割したペインでは新たに tmux 経由でシェルが実行されるので環境変数が引き継がれないなどあるけど、だいたい便利に使えている。

時間がかかったコマンドの実行終了時に自動で time する

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L15

REPORTTIME に指定した時間より長くかかったコマンドの終了時に、自動的に time の結果を出力する。

日本の伝統色を使ったカラースキーム: Japanesque

Vim

GitHub - aereal/vim-colors-japanesque: The colorscheme featuring Japanese traditional colors.

https://raw.githubusercontent.com/aereal/vim-colors-japanesque/master/screenshots/ruby.png

日本の伝統色を使ったカラースキーム: Japanesque

GUI 版の Vim 向けカラースキーム・Japanesque を作った。

iTerm 2 で使えるカラースキーム、Japanesque を作った - Sexually Knowing

以前、iTerm 向けに作った同名のカラースキームを踏襲しつつ新たにパレットから作った。

コンセプトとしては:

  • 十分なコントラストが確保されていること
  • 着目すべき構文要素が適切に目を引くような配色であること

……とした。

インストール方法

" neobundle.vim
NeoBundle 'aereal/vim-colors-japanesque'
" Vundle
Bundle 'aereal/vim-colors-japanesque'
" vim-plug
Plug 'aereal/vim-colors-japanesque'

あるいは colors/japanesque.vim$VIMRUNTIME/colors/ 以下に配置するなど。

カラーパレット

https://raw.githubusercontent.com/aereal/vim-colors-japanesque/master/screenshots/palette.png

  • 紫紺
  • 菜の花
  • 銀鼠
  • 今様
  • 鬱金

……以上の9色を基本のカラーパレットとしている。

紫は、古来から高貴であるとされて尊ばれていること、カラーパレット上のどの色との相性がよいこと、寒色とも暖色ともつかず機能的に中立的であることなどから背景色として、カラースキームの方向性を決定づける役割を与えた。

カラーコードは引き続き NIPPON COLORS - 日本の伝統色 を参照している。

見やすいカラースキームを作る上で考えること

もともと Solarized の暗い背景モードの配色を気に入っておらず、カラースキームを作ることを考えていたところに上記の記事を読んで共感し、iTerm のカラースキームとして作って気に入っていた Japanesque を Vim 向けに一から作ろうと決めた。

カラーパレットを上記の通り決めて、次に構文要素にどのような色 (役割) を与えるか考える。

カラースキームは、ソースコードの注目すべき領域とそうでない領域を色で表現することで、理解を助けるはたらきがあると思う。つまり、ソースコード中でどのような要素に着目すべきか考えるとよさそう。

ソースコードに出現するトークン (識別子) は大きく分けて:

  • 言語組み込みの制御構造
  • モジュール化の仕組み
  • 再利用可能なコンポーネント
    • 例: クラス
  • ローカル変数

……のように分類できると考えてみる。

上から下に行くほど情報の凝集度が高まり、生存期間は短くなる。制御構造はプログラムの実行開始から終了まで消えることはないだろうし実質的に無限と考えられる。

情報の凝集度が高いほど重要なので、識別子としてより目立たせるという作戦を立てた。制御構造などのコントラストは下げ、ローカル変数やプロパティはコントラストを上げるという作戦にした。
Rubyスクリーンショットを見ると雰囲気は伝わると思う。

関連

課題

現時点では:

  • CLI 版のサポート
  • lightline.vim 向けカラースキームの追加

……が欠けているので対応しようと思っている。

他、優先度は高くないが色覚障害の対応状況はチェックしたい。

その他エディタへの移植リクエストやバグ報告などは GitHub の Issues にいただけると助かります。ほか Twitter でも。

PageSpeed Insights のスコアを Mackerel に投稿するアプリケーションをボタン1つで Heroku にデプロイできる状態にした

GitHub - aereal/psi-metrics: Post PageSpeed Insights score to Mackerel

PageSpeed Insights とは

PageSpeed Insights

ユーザが体感するページの読み込み速度を100点満点で評価するサービス。画像を圧縮せよとか HTTP キャッシュを使えとか、とにかく Web ページを速くするためのアドバイスをしてくれる。

チームで指標のひとつとして月1で眺めている。

API

Google PageSpeed Insights には API がある。

Web のインターフェースでとれる情報はほとんど JSON で返ってくる。スコアだけではなくて、各ルール (例: 画像を圧縮せよ) ごとのスコアと説明も構造化されて含まれているのでその気になれば Google PageSpeed Insights の Web 版を作り直すこともできる (やりたくはない)。

Mackerel のサービスメトリクスに

毎月1回しか見ていなくて、そのあいだにどういう変動があったかは追えていない。とはいえ毎日見たくはない。

そこで Mackerel のサービスメトリクスにする。ルールごとのスコアもあるがひとまずトータルのスコアだけ見れればよさそう。

サービスメトリクスにした様子:

f:id:aereal:20160309104743p:plain

Deploy to Heroku

できたものの定期的に実行する必要があるので手頃な Heroku にデプロイすることにした。とはいえ過去の経験からして素朴に `heroku config:set ...` などしていくと、後になってどういう設定をすればまず動くのか、といったことがわからなくなるので対策しなければならなかった。

そこで Deploy to Heroku ボタンのことを思い出した。

Introducing Heroku Button | Heroku

リポジトリに app.json を書いておくと必要な設定をフォームで埋めて送信するだけでアプリケーションがデプロイできるというもので、設定の説明を書いてなおかつ再利用なかたちになるのでさっと app.json を用意した。

app.json や Deploy to Heroku ボタンは Heroku のドキュメントがわかりやすい:

Creating a 'Deploy to Heroku' Button | Heroku Dev Center

app.json Schema | Heroku Dev Center

おわり

Deploy to Heroku ボタンを押していますぐ Mackerel で PageSpeed Insights のメトリクスを作りパフォーマンスの監視をしてみましょう。

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

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 で実行するスクリプトをリポジトリに含めるときに古いブランチでも実行できるようにする

Git

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) をリリースした

Perl

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 型として推論するようにした

Perl

metacpan.org

github.com

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

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

github.com

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

どうぞご利用ください。

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

Perl

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 の型を推論するモジュールを書いた

Perl

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

感想

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

どうぞご利用ください。