TIM Labs

問題

世の中にはシンタックスハイライトを行うツールが既に多数存在しています。例えば以下のようなものがあります:

メジャーな言語やフォーマットなら標準でシンタックスハイライトの設定が同梱されていますが、 ニッチな言語やフォーマットだとまずそのような設定は存在しません。 それならば独自に設定を書けばいいのですが、 大抵のツールでは構文の定義方法が特定のパーツに該当する正規表現を並べるだけなので、 言語によっては構文の妥当な記述が不可能な場合もあります。

となると独自に実装せざるを得ません。 例えば Vim の :help ドキュメントを良い感じに Web ブラウザ上で見るためのツール を作って Heroku で動かそうと思った場合、 Vim の :help ドキュメントの構文のいい加減さ から、まず既存のツールを利用してのシンタックスハイライトはできません。 さらにこのツールの場合はリンク周りもあれこれ面倒を見る必要があるため、 ますます既存のツールの利用はできません。

という訳で元のソースをパースしていい感じにシンタックスハイライトする仕組みを作る必要があります。 ことパースに関して言えば Ruby には様々な gem が存在しているので、そのどれかを使うことになります。 パース関連で言うと以下のような gem があるのですが:

  • treetop (Lex/Yacc のように独自 DSL を Ruby のコードへコンパイルする必要がある。ダサい)
  • citrus (treetop と同様のダサさ)
  • rsec (Ruby 上の DSL で構文の記述を行う。でもその DSL がダサい)
  • parslet (Ruby 上の DSL で構文の記述を行う。 DSL が超COOOOOOOOOL。構文エラー時の出力もCOOOOOOOOOL)

という感じなので parslet 一択という状態です。 そういう訳で parslet を使って Ruby でシンタックスハイライトを実装してみましょう。

みなさん仕事でPhantomJS使っていますか?僕は使っていません。でもそろそろ使ってみたいので、仕事でよく使うCentOSにPhantomJSをインストールする方法を調べてみました。

環境

今回はVirtualBoxes - Free VirtualBox® Imagesで公開されているCentOS-6.2のイメージを利用します。

PhantomJSをインストールする前に、あらかじめ次のコマンドでライブラリを更新と開発ツールのインストールを行います。依存関係などが面倒なので、開発ツールは全部インストールします。

sudo yum upgrade
sudo yum groupinstall 'Development tools' 'Additional Development'

問題

パッチのレビューなどで Git の diff の出力を読む機会はそれなりにあると思います。 その際、 diff で列挙されている内容だけでなく前後のコードも確認するために変更のあったファイルを開くことも多々あるでしょう。

Vim にはこの状況にぴったりのコマンド gf があります。 gf はカーソル下にあるテキストからファイル名らしき文字列を探してそれを開くというコマンドです。 diff の出力には変更のあったファイルのパスが含まれていますから、 そこへカーソルを移動して gf を使えば良いというわけです。

gf はとても便利なコマンドではあるものの、 上記の操作を何度か行っていると不満が募ってきます。 というのも、以下のような手間があるからです:

  • gf を実行するためにパスの書かれている位置までカーソルを移動しなければならない。
  • gf でファイルを開いた後、レビューしたい場所までカーソルを移動しなければならない。

例えばカレントバッファに以下の内容が含まれているとしましょう (左端の数字は行番号なので適宜無視してください):

 1 diff --git a/autoload/gf/diff.vim b/autoload/gf/diff.vim
 2 index 469fdb3..b135316 100644
 3 --- a/autoload/gf/diff.vim
 4 +++ b/autoload/gf/diff.vim
 5 @@ -21,7 +22,7 @@
 6  "     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 7  "
 8  " Interface
 9 -function! gf#diff#go_to_hunk(type)
10 +function! gf#diff#go(type)
11    let d = gf#diff#investigate_the_hunk_under_the_cursor()
12    if d is 0
13      echomsg 'There is no diff hunk to jump.'
14 @@ -113,7 +114,7 @@ function! gf#diff#investigate_the_hunk_under_the_c
15        return 0
16      endif
17      let [d.from_path, d.to_path] = xs
18 +  call setpos('.', original_position)
19 -  call setpos(original_position)
20
21    return d
22  endfunction

このとき:

  • 12行目で gf をすると autoload/gf/diff.vim の25行目まで移動する。
  • 16行目で gf をすると autoload/gf/diff.vim の117行目まで移動する。

のような感じで 手作業によるカーソル移動なしに目的の位置へ移動 できればとても便利です。 このように gf の動作を拡張するにはどうすれば良いでしょうか。

問題

世間一般のエディタではカーソルを行頭や行末に移動するキーが C-a だったり C-e だったり Home だったり End だったりと、 それなりに押下し易い配置になっているのですが、 Vim では何を思ったのか ^$ という謎のキー割り当てになっています。 この謎のキー割り当てにはそれなりの理由があると思われるのですが、 時々不便に思う場合があります。

例えば「カーソル行のテキストをコピーして他のアプリケーションにペーストする」というシチュエーションを考えてみましょう。 コピー自体は "*yy ですぐできるのですが、これでは改行文字が含まれるため、ペーストした際に悲しい結果を招くことが多々あります。 ^vg_"*y0v$h"*y とすれば改行文字抜きでコピーはできるものの、 キー押下難易度の高さにストレスが溜まってしまいます。 どうにかして快適にカーソル行のテキストを選択/処理できないものでしょうか。

Pryは結構前からgithubのリポジトリを追いかけている人達には認知されていましたが、RailsCastsでも紹介されたことから、Ruby界で一気に広がりを見せています。 ちなみに発音はpra'i(ぷらい)です。英単語で「覗く」などを意味します。

今回はそんな便利なPryについて少し紹介したいと思います。

問題

Reactive Extensions で非同期処理を簡潔に記述する では「キー入力に応じて補完候補を表示する」「ただし補完候補はAjaxで非同期に取得する」という いまどきのWebアプリケーションにならあって当然の機能が、 Reactive Extensions (Rx) を使うことであたかも普通のリスト処理のように記述できることを示しました。

入力補完は例としては単純で分かり易いものの、 もう少し別の例も欲しいところです。 という訳で、 Rx を使うことで コナミコマンド を実装してみましょう。

問題

シェルはお友達です。 一見すると役に立たないように思えるコマンドでも、 組み合わせ次第で複雑な処理をこなすための道具になります。

例えば

  • head - ファイルの先頭10行を出力する。
  • cut - ファイルの各行のうち特定の部分を選んで出力する。
  • shasum - SHA-1 ハッシュ値を出力する。

という、一つ一つでは大して役に立ちそうにないコマンドも、

 head /dev/urandom | shasum | cut -f 1 -d ' '

このように組み合わせることで「ランダムなパスワードを自動生成する」という偉業を達成することができます。

シェルをある程度使っていると上記のようなワンライナーをしばしば入力することがあります。 覚え易いものや短いワンライナーなら即席で入力しても構わないのですが、 先ほどの例のようなものになると少々入力するのが面倒です。 そこで役に立つのがコマンドラインの入力履歴です。 入力履歴があれば、例えば 三か月前に試行錯誤の末に編み出した git filter-branch でリポジトリを消毒するワンライナー であっても、簡単に再実行することができます。

入力履歴は大変便利なのですが、 こと検索においては欠点があります。 コマンド一つ一つが汎用的で小粒なために、 いざ検索しようとすると適切なキーワードが閃きづらい ということです。 パスワード生成の例では shasum が比較的出現頻度の低いコマンドなので、 これをキーワードにして検索すればまだ見つけ易い方ではありますが、 shasum を含む応用例は他にもあるため、 一発で目的のワンライナーに辿り着けることは多くありません。 素早く目的のワンライナーを見つけ出すにはどうすればよいのでしょうか。

問題

今時の若者ならば Heroku 等を利用して手早く Web アプリを作成・公開することが日常茶飯事です。 バックエンドもフロントエンドも今はフレームワークが充実していますから、 高度な処理を簡潔な記述で行うことができます。

しかし非同期処理となると話は別です。 例えばフロントエンドを作るとなると、まず jQuery を使うことになるでしょう。 jQuery は洗練された API で DOM 操作を簡単に行うことができますし、 非同期通信についても $.ajax を使えば煩雑なことほぼ知らずに済みます。 例えば Wikipedia の検索フォームは入力補完が行われるようになっており、 検索フォーム文字が入力されると関連するページのタイトルが候補として表示されます。 このような処理を書くとなると以下のようなコードになるでしょう:

var showCompletionMenu = function (words) {
  ...
};

var completeWords = function (partialWord) {
  $.ajax({
    url: 'http://en.wikipedia.org/w/api.php',
    dataType: 'jsonp',
    data: {
      action: 'opensearch',
      format: 'json',
      limit: 100,
      search: 'foo',
      success: function (data) {
        var words = data.data[1];
        showCompletionMenu(words);
      }
    }
  });
};

var $form = $('#userInput');
$form.keyup(function () {
  completeWords($form.val());
});

このように、イベント処理や非同期通信を行う場合、

  • イベントの監視を始める部分($form.keyup)と発生したイベントを処理する部分(completeWords)
  • リクエストを送信する部分($.ajax)と受信した結果を処理する部分(showCompletionMenu)

を分離し、前者にコールバック関数として後者を渡す形になります。 普通のプログラムならば上から下へ順次処理が行われるのですが、 このように非同期処理が絡むと実行の流れはソースコードの見た目から背離します。 この単純な例ですら非同期処理が2段も積み上がっており、 とても反射的に読んで意味を理解できるソースコードではありません。

ここで本当に行いたいことは補完候補の表示であって、 その表示タイミングや補完候補のデータの出所は重要ではありません。 しかしコールバックという名の中間層を噛まさざるを得ないため、 本質からやや遠ざかったソースコードになっています。 どうにかしてこの状況を打破できないでしょうか。

問題

美しいプログラムを書く(リスト処理ライブラリ編) では「ちょっとした数のデータを相手に何か処理を行う」(=リスト処理を行う)ことを例題に、 既存のライブラリを適切に使えばコードを簡潔かつ明瞭にできることを示しました。 一度コツを掴めば開発効率がどんどん向上します。

ところが、現実には複数の言語を並行して利用する機会は少なからずあります。 不慣れな言語でコードを書いていると 「あの言語でいうところのアレはこの言語でいうとどうなるんだろう」 と思うことが頻繁に発生します。 特にリスト処理については頻出しますから、 各言語での書き方の比較表が欲しいところです。 しかし探してみても案外見つかりません。

という訳で見つからないなら作ってしまいましょう。

Android 指リスト編については、既に Haskell編Python編Python編(その2) が存在しているが、今度は C++ 編である。

が、 C++ では Haskell や Python のように、 interactive shell でお遊びをするあたりはさすがに再現できないので、 代わりに実行速度にこだわってみたい。

実行速度にこだわるというからには、 Python やら Haskell やらがどの程度の実行速度なのかをあらかじめ知っておく必要がある。 以前の記事では interactive shell で遊ぶために全てのパターンを列挙していたが、 今回については単純化のためパターン数の計算のみについて考えることとし、 最初から単純にパターン数を数え上げる方式にプログラムを若干変更の上計測する。

実行時間計測:Python

ベースのプログラムはPython編(その2)を用い、 nextList 関数を数え上げのみに限定しておく。

def nextList(i, rem):
    return sum(
        (nextList(s, rem - set([s])) for s in candidate(i, rem)),
        1 if len(rem) <= 5 else 0)

print(nextList(0, frozenset(range(1, 10))))

time コマンドで実行にかかった時間を計測する。

% time python3 flistnum.py
389112
python3 flistnum.py  2.65s user 0.02s system 99% cpu 2.667 total
% time python3 flistnum.py
389112
python3 flistnum.py  2.66s user 0.00s system 99% cpu 2.659 total
% time python3 flistnum.py
389112
python3 flistnum.py  2.65s user 0.01s system 99% cpu 2.657 total

2.6秒強程度といったところ。

実行時間計測:Haskell

ベースのプログラムはもちろんHaskell編のものを使用する。 Python のときと同じように nextList 関数を数え上げのみに限定し、

nextList :: Int -> [Int] -> Int
nextList n rem =
    foldr (+) (if length rem <= 5 then 1 else 0)
    [nextList s (rem \\ [s]) | s <- candidate n rem]

main = print $ nextList 0 [1..9]

実行速度最適化オプション -O2 もつけてコンパイル後、計測する。

% time ./flistnum
389112
./flistnum  0.10s user 0.00s system 101% cpu 0.102 total
% time ./flistnum
389112
./flistnum  0.09s user 0.01s system 99% cpu 0.102 total
% time ./flistnum
389112
./flistnum  0.10s user 0.00s system 101% cpu 0.101 total

ちょっ...、わずか 0.1 秒...。

やるな、Haskell。 だが今回使うのは何しろ C++ である。コレに勝ってみせねばなるまい。

C++ の本当の実力を見せてやる!

最近のコメント