北市真
Middleman Blog
https://diary.kitaitimakoto.net/
アペフチ
2020-05-03T00:00:00Z
<div class="paragraph">
<p><a href="https://joinplu.me/">Plume</a>というブログエンジンを立ち上げてみた。暫くそちらでブログ記事を書こうかと思う。そのうちこちらに戻ってくるかも知れないけど。</p>
</div>
<div class="paragraph">
<p>PlumeはRust製のブログエンジンで、ActivityPub(丁度いい塩梅の紹介が見付からないけど、<a href="https://qiita.com/nullkal/items/accc5d62836a930b3cd9">ActivityPubの概要</a>とかいいかな)対応している。だから、僕が立ち上げたインスタンスと、他の人が立ち上げたインスタンスが繋がることができて、お互いのインスタンス上にいるユーザーがフォローしあったり、いいねしたりブースト(リブログ。ツイッターで言えばリツイート)したりできる(勿論、同じインスタンス内でもできる)。Plume同士じゃなくても、Mastodonからフォローしたりもできる。筈なんだけど結構バグがあって、直しながら運用してます。</p>
</div>
<div class="paragraph">
<p>WordPressとも相互に繋がってフォローできるらしいので、勿体ないね(<a href="https://github.com/Plume-org/Plume/issues/567">Compatability with AP-enabled Wordpress blogs #567</a>)。</p>
</div>
<div class="ulist">
<ul>
<li>
<p>ブログエンジン(サイト)はこちら: <a href="https://blogs.kitaitimakoto.net/">コンコニ</a></p>
<div class="ulist">
<ul>
<li>
<p>誰でもユーザー登録してブログ作れるようにしてあります。スパム対応に疲れるようなことがあったら新規受け付けは閉じる。</p>
</li>
</ul>
</div>
</li>
<li>
<p>その中の僕のブログはこちら: <a href="https://blogs.kitaitimakoto.net/~/Apehuci">アペフチ</a></p>
</li>
</ul>
</div>
https://diary.kitaitimakoto.net/2020/05/03.html
Plumeというブログエンジンを試してる
2020-05-03T00:00:00Z
2020-05-03T00:00:00Z
<div class="paragraph">
<p>『<a href="https://tatsu-zine.com/books/flutter-firebase">Flutter×Firebaseで始めるモバイルアプリ開発</a>』という本を読み始めて、ソースコードが白黒なのに気が付いた。
<span class="image"><img src="https://gyazo.com/566feb3f8fe1166b79d0682d6b846741.png" alt="シンタックスハイライトされていないソースコード"></span></p>
</div>
<div class="paragraph">
<p>ので、例によって、シンタックスハイライトしてみました。
<span class="image"><img src="https://gyazo.com/a75b247f8a823a7371ba9d4064261a85.png" alt="シンタックスハイライトされたソースコード"></span></p>
</div>
<div class="paragraph">
<p><a href="https://gitlab.com/KitaitiMakoto/pirka">Pirka</a>というコマンドラインツールを使います。インストールは <code>gem</code> コマンドで。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code>% gem install pirka</code></pre>
</div>
</div>
<div class="paragraph">
<p>次に、シンタックスハイライト情報が入っている「ライブラリー」ファイルを更新します。既にPirkaを入れてくれている人も、今回新しくライブラリーを追加したので、この操作は必要です。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code>% pirka update</code></pre>
</div>
</div>
<div class="paragraph">
<p>最後にダウンロードしてきたファイルを引数に、 <code>pirka</code> コマンドを実行します。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code>% pirka ~/Downloads/Flutter×Firebaseで始めるモバイルアプリ開発-1.0.0.epub</code></pre>
</div>
</div>
<div class="paragraph">
<p>すると、本の中のコードがハイライトされています。(上書きしちゃうので、必要であればバックアップしておいてください。)</p>
</div>
<div class="paragraph">
<p>僕は<a href="https://tatsu-zine.com/">達人出版会</a>で買って、そこからダウンロードしたEPUBファイルを元にハイライト用のデータを作ったので、他の所で購入した人はもしかしたらうまくいかないかも知れない。</p>
</div>
<div class="amazlet-box" style="margin-bottom:0px;"><div class="amazlet-image" style="float:left;margin:0px 12px 1px 0px;"><a href="https://tatsu-zine.com/books/flutter-firebase" name="amazletlink" target="_blank"><img src="https://tatsu-zine.com/images/books/1020/cover_s.jpg" alt="Flutter×Firebaseで始めるモバイルアプリ開発" title="Flutter×Firebaseで始めるモバイルアプリ開発" style="border: none;" /></a></div><div class="amazlet-info" style="line-height:120%;margin-bottom:10px"><div class="amazlet-name" style="margin-bottom:10px;line-height:120%"><a href="https://tatsu-zine.com/books/flutter-firebase" name="amazletlink" target="_blank">Flutter×Firebaseで始めるモバイルアプリ開発【電子書籍】</a></div><div class="amazlet-detail">下畑 翔, わみ<br />インプレスR&D<br />発行日: 2018-12-21<br />対応フォーマット: PDF, EPUB<br /></div><div class="amazlet-sub-info" style="float:left;"><div class="amazlet-link" style="margin-top:5px"><a href="https://tatsu-zine.com/books/flutter-firebase" name="amazletlink" target="_blank">詳細を見る</a></div></div></div><div class="amazlet-footer" style="clear:left"></div></div>
<div class="paragraph">
<p>相談には乗りますので、次のどちらかにご連絡ください(他のでもいいけど、この二つだと嬉しい)。</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="https://gitlab.com/KitaitiMakoto/pirka-library/issues" class="bare">https://gitlab.com/KitaitiMakoto/pirka-library/issues</a></p>
</li>
<li>
<p><a href="https://bookwor.ms/@KitaitiMakoto" class="bare">https://bookwor.ms/@KitaitiMakoto</a></p>
</li>
</ul>
</div>
https://diary.kitaitimakoto.net/2020/02/16.html
『Flutter×Firebaseで始めるモバイルアプリ開発』をシンタックスハイライトして読む
2020-02-16T00:00:00Z
2020-02-16T00:00:00Z
<div class="paragraph">
<p>ふと「矢を止める五羽の梔鳥」(舞城王太郎)を読みたくなって、検索したら『みんな元気。』に入っているとのことだったので、Kindleで買った。</p>
</div>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E3%81%BF%E3%82%93%E3%81%AA%E5%85%83%E6%B0%97%E3%80%82-%E8%88%9E%E5%9F%8E%E7%8E%8B%E5%A4%AA%E9%83%8E-ebook/dp/B00CL6N332?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklogjp-default-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B00CL6N332" target="_blank"><img src="https://images-fe.ssl-images-amazon.com/images/I/51c5-vZ1u9L._SL160_.jpg" width="104" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E3%81%BF%E3%82%93%E3%81%AA%E5%85%83%E6%B0%97%E3%80%82-%E8%88%9E%E5%9F%8E%E7%8E%8B%E5%A4%AA%E9%83%8E-ebook/dp/B00CL6N332?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklogjp-default-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B00CL6N332" target="_blank">みんな元気。</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="https://booklog.jp/author/%E8%88%9E%E5%9F%8E%E7%8E%8B%E5%A4%AA%E9%83%8E" target="_blank">舞城王太郎</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">新潮社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2007-06-01</div></div><div class="booklog_html_link_amazon"><a href="https://booklog.jp/item/1/B00CL6N332" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<div class="paragraph">
<p>(表紙に全然見覚えがない。僕は本当にこれを読んだことがあるんだろうか。)</p>
</div>
<div class="paragraph">
<p>と、書いてはみたけれど、実のところこの小説を読みたかったのかは分からなかった。</p>
</div>
<div class="sect2">
<h3 id="_矢を止める五羽の梔鳥">矢を止める五羽の梔鳥</h3>
<div class="paragraph">
<p>舞城王太郎の小説は読みやすいし分かりやすいのだけど、「何か全然意味分からないのあったなあ」という気持ちの記憶があって、それと紐付いている言葉が「<ruby>梔<rp>(</rp><rt>くちなし</rt><rp>)</rp><rt></ruby>鳥」だったので、検索して出てくる「矢を止める五羽の梔鳥」だな、と思って、これを読みたかったのだと思い込んでいた。思い込んでいたというのは、実際読んでみたら、これじゃなかった。内容はもう殆ど忘れていたし、面白かったので、読んでよかったなとは思ったけど、これではなかった。</p>
</div>
<div class="paragraph">
<p>夜山火事を見ていたらあれよあれよと街(多分、小さな集落。いつもの舞城王太郎なら)で起こってる連続殺人と関連を持ってしまった語り手が、舞城王太郎得意の<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>「言葉遊び的な頓智」で解決したんだかしなかったんだか……という話。</p>
</div>
<div class="paragraph">
<p>「昔読んだあの小説が読みたい」の「あの小説」を除いた「小説を読みたい」の気持ちもまだ全然抱えたままだったので、そのまま他のを読んでみようと思って、目次を見てみる。</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">みんな元気。</dt>
<dd>
<p>面白かった記憶はある。家族の話だった筈。</p>
</dd>
<dt class="hdlist1">Dead for Good</dt>
<dd>
<p>全然思い出せない。</p>
</dd>
<dt class="hdlist1">我が家のトトロ</dt>
<dd>
<p>全然思い出せない。</p>
</dd>
<dt class="hdlist1">矢を止める五羽の梔鳥</dt>
<dd>
<p>今読んだ奴。</p>
</dd>
<dt class="hdlist1">スクールアタック・シンドローム</dt>
<dd>
<p>分かりやすくてすかっとする奴だった気がする。</p>
</dd>
</dl>
</div>
<div class="paragraph">
<p>(もし読みたかった小説がこの本に入ってるとして)消去法で、候補は「Dead for Good」と「我が家のトトロ」だ。</p>
</div>
</div>
<div class="sect2">
<h3 id="_我が家のトトロ">我が家のトトロ</h3>
<div class="paragraph">
<p>名前がキャッチーだったのでこちらから読んだ。外れたら「Dead for Good」を読むとも決めてなかったしそもそも必ずしも例の「読みたかった小説」を求めてたわけじゃなくて何となく気になったから読んだので「こちらから」というのはおかしいけど。</p>
</div>
<div class="paragraph">
<p>冒頭が駅のトイレでうんこ漏らすというエピソードで、そこまで読んでもう思い出して、当時は知らなかったけど後から<a href="http://www.nakamurahiroki.com/2010/04/31.html">31歳にしてうんこをもらしました</a>という話を知って「ああこれかあ」となって、でも別に再読しなかった作品。主人公は(もらしました記事と同じ)コピーライター出身で、元同僚、現小説家の濱田淳という人とこんなやり取りをしていたのは読まずとも思い出せた。</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>「面白い小説書いてたって、それが他の人に気づいてもらえなかったら駄目だろう」と僕は言う。<br>
「面白い小説が書ければ、皆気づきますよ」と濱田は言う。「当然じゃないですか。皆面白い本が読みたくて探してるんだから」<br>
「甘いよ。いいものがあれば皆がそこに自然に集まるようなら、いいものだけが売れるようなら、広告もコピーもそもそもいらないだろ。俺らだって生きていけてなかっただろ。でも広告はある。何でかって言うと、それが必要だからだろ。いいもんだって、放っておいたら誰も気付かないんだよ。だから広告が要るんだ」<br>
「逆ですよ。広告があるせいでつまんないものに人の目が行くんですよ。本当なら人の目はいいものだけを探しているのに、偽物に騙されて良くないのもにも目が行っちゃって、いいものに気付きにくくなってるんですよ」<br>
「何言ってやがんだ馬鹿。お前さ、自分が広告打ってきた商品、つまんないもんだと思ってた訳?だとすればお前の広告が間違ってたんだよ。俺は俺の広告が正しいと思って打ってたんだから。いいものだから皆に伝えたいって気持ちで広告打ってたんだから」<br>
「まーた上口さんはそんな、綺麗事ばっかり。そんなの絶対嘘ですよ」<br>
「嘘じゃないよ」</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p>……と議論は続く。少年まんがで育ったためか僕は視点人物に感情移入して読むんだけど、初めて読んだ当時「面白いだけじゃだめだ」という、自分とは反発する価値観を視点人物が語りだして、感情的に混乱したのを思い出した。</p>
</div>
<div class="paragraph">
<p>今読むと、視点人物の方が自分の言っているのことをきちんと自分の中から出した物なのか反射的に正当化しちゃうために出した物なのか分からなくて、それはこの後の議論を読んでも分からないし地の文でも後から「実は……」とか「本当なのに……」とかは書かないから分からなくて、そこが面白い。読者に考えさせるから面白い、のではなくてそんなこと大事なことじゃないんだよという態度がいいなあと思う。</p>
</div>
</div>
<div class="sect2">
<h3 id="_dead_for_good">Dead for Good</h3>
<div class="paragraph">
<p>これが目当ての小説だった。同じ本に入っていてラッキーだった。……とは思うんだけど、もしかしたら偶然ではないのかもなとも思う。前から順番に「みんな元気。」「Dead for Good」と読んで「んんん?」となったけどそのまま「我が家のトトロ」「矢を止める五羽の梔鳥」と来て、タイトルの説明(<a href="http://jpsekaiisan.com/category6/entry203.html">龍安寺のつくばいに刻まれた『吾唯知足』から学ぶ人生の深い意味とは・・・</a>)が面白いなと思ったその視覚的な興奮と「んんん?」という感情的なもやもやを残していたのが結び付いて記憶に残っていたのかも知れない。人間っぽい。</p>
</div>
<div class="paragraph">
<p>特に盛り上がりもないし、体験間の繋がりも特にない日常を描いていって、それは気持ちのいい物では全然ないし、だからといって考えさせるというのも違って、意味とか作者のメッセージという物を求めていた当時の僕は全然分からなかった。最初にも書いたけど舞城王太郎は分かりやすいし面白いという印象を持っていたから、それに反した感じの小説で驚いた。のだけど、今読むと昔ほど分からないって感じじゃない。小説が分かるというのは小説を読むことでしか分からないので、言葉では表さないけど、前読んだ時より分かる。関係ないエピソードは響き合ってる。</p>
</div>
<div class="paragraph">
<p>こういう感じは今ならはっきりと僕は好きだなと言えるし、「Dead for Good」の時は分からなかったけど「美味しいシャワーヘッド」<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup>の面白さに通じていたのだなと今なら思う。</p>
</div>
<div class="paragraph">
<p>ややオフトピックだけど、作中の重要人物である<ruby>兼益<rp>(</rp><rt>かねます</rt><rp>)</rp></ruby>のようなひとについて考えることがある。兼益は本物のサディストで、主人公を騙して薬で意識を奪って電流を流して後遺症を残させたり、今ではどこか紛争地域でアンチテロリストとしてテロリストを拷問したりしている、どうしてもそうしないといられない人間だ。そういう人はいる、と思う(会ったことはないか少なくとも気付いたことはないけど)。そういう人は例えば日本のような平和な国<sup class="footnote">[<a id="_footnoteref_3" class="footnote" href="#_footnotedef_3" title="View footnote.">3</a>]</sup>に生まれてしまってはその欲求を満足させることは一生できないんだろうか。それは幸せなんだろうか。その人が欲求を満足させることは絶対に許されないことなんだろうか。つまり、社会に仇為す生理的欲求は、満たされてはいけないんだろうか(この場合、それが許される国に行くというのは話を逸らせているだけ)。ということを、たまに考える、というより、ぼんやり思う。これを考えるのは僕自身が今までほんと幸せな環境で暮らしてきたということを示唆するのは分かってはいるけど、考えてしまう。落ちはない。</p>
</div>
<div class="paragraph">
<p>特に好きだなと思ったシーンは主人公が風呂場にいる所。別に彼氏のいる女の子が、主人公のことが好きだということをその彼氏にして、彼氏が主人公の部屋の前に来てドアをどんどん叩いている時、主人公は風呂場のバスタブに座って包丁を持ったまま、その彼氏が入って来るのを期待している。バスタブというのは兼益に押し込められて電流を流されて後遺症を齎した場所で、ドアを叩いているのが女の子の彼氏でも、包丁を握って座り込んで入って来るのを期待している相手は兼益だ、と自分で分かっているというシーン。いいな。</p>
</div>
</div>
<div class="sect2">
<h3 id="_みんな元気">みんな元気。</h3>
<div class="paragraph">
<p>「Dead for Good」を読んで、小説を読みたい気分がまだ続いていたので折角だからと表題作も読んだ。</p>
</div>
<div class="paragraph">
<p>ある日別の家族がやってきて、自分の妹とその家族の男の子を強制的に交換して去っていった……という所からスタートする、家族という感覚についての話。かな。</p>
</div>
<div class="paragraph">
<p>なんか昔ほどのめり込めなくて、途中飛ばしながら読んでしまった。交換されていなくなってしまった妹への思いと、交換されて弟になってしまった少年への思いは、日本語になっている家族愛とは違うけどやっぱりそこにそれなりの形の愛情はあるのだな、だからみんな元気。という話で、そう感じ取った感覚はよく憶えていた<sup class="footnote">[<a id="_footnoteref_4" class="footnote" href="#_footnotedef_4" title="View footnote.">4</a>]</sup>。のだからちょっと退屈に感じてしまったのだろう。主張の分かっている話を読んでもな……というのと、その感覚自体は今の僕には当たり前だから。</p>
</div>
<div class="paragraph">
<p>でも結構、本題とは違う、昔は気にもしなかったような箇所が今読むと面白くて、そういう面白さがあるのは本物の小説だなあ。</p>
</div>
<div class="paragraph">
<p>主人公(枇杷)には妊娠期間七か月で生まれて同じ学年の姉(ゆり)がいて、顔が似ていて、その姉の彼氏が自分に色目を使ってきたり逆があったりが毎回で、嫌になって付き合ってもセックスをしなくなった、という経歴があるのだけど、彼氏との会話でこういうのがある。</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>私が思うに、宮本いさおのいいところは大きな手と何かたくらんでそうに見えて全然たくらみなんてない変な顔と腹のすっきりしたところで、私が雨の中を傘をささずに歩いていると「そういうのが楽しければ好きにすれば?」とか言いながら自分の傘も閉じて仏頂面を作って黙って歩いて百メートル先で突然「いさおもうパンティまで濡れ濡れ」とかいきなり言って爆笑を誘うところだったし、野口健司のいいところは鼻筋がまっすぐ通っていて先が丸くて、キスするときにその鼻先がとんとん、とんとん、と私の頬、胸、腹、太ももをノックしてくれることや、寝言で「こら、そこのカレーからいい加減その猫つまみ出せ」と怒ってたりすることや、うっかり自分がおならをしちゃったときに、その部屋やら車両やら道ばたから私の手を引っ張り、ときにはお姫様だっこで「ギャー逃げろ」とか言って駆け出したりすることだったし、他の彼氏にもいろいろ細かい楽しいところ、観察すべきところがあったのだが、完璧の南田洋平に言わせれば、宮本いさおも野口健司もその他の元彼たちも、恋愛の不可能性においてこそ私を惹きつけるらしい。<br>
「ゆりちゃんの元彼とか今彼となんて絶対嫌と言いながら、その絶対嫌なところでしか枇杷は相手を選べなかった訳よ」と南田は言う。「と言うか、選ばなかった訳だ。うまくいかないからこそいいんだよな。ゆりちゃんの元彼と今彼以外のところで付き合いが始まると、わざわざゆりちゃんの元彼今彼のことを話題に持ち出してきて問題にして、問題ないところにいちいち問題作って結局駄目にしちゃったりする。そういうの見てれば分かるけど、《ゆりちゃんの元彼とか今彼》なんて、枇杷にとっては男と別れるための道具に過ぎないんだよな。で、その道具で磨いて余計な部分を落として発展させて出てきたセックスレスも、結局同じ道具なんだよ」<br>
日曜日の朝に、調布のパルコのレストランで、桜が散って落ち着いて、日差しの中にいるのにそんな会話が始まって、私は言う。「これって別れ話じゃないよね」<br>
「違うよ」と南田が言う。「ただの会話。でもただの会話だけど、単なる会話じゃなくて、俺ちょっとはっきりさせておきたいんだよね」<br>
「私の問題とか?」<br>
「全然違う。俺は、枇杷の持ち出すどんな道具にも負けないし、道具なんかいくら使ったって枇杷のことを諦めたりしないってこと」<br>
「ありがとう」<br>
「感謝されるようなことでもないよ。そういうことじゃない。俺を諦めさせようとするなら、もっと本当の理由を持ってこなきゃいけないってこと」<br>
「本当の理由って?」<br>
「唯士君とどうするか、ちゃんとはっきりさせなきゃいけないってこと」<br>
南田はあっという間に全部言ってしまう。何も待たない。譲歩はないが、ごまかしもない。先延ばしもないが、せき立てる感じもない。南田がそれを持ち出すと、このときこそそれを持ち出す最良のタイミングだったんじゃないかと思う。そしてそれはたぶん、南田の人格とは関係なく、実際に最良のタイミングだったのだ。遅すぎたくらいだとすら私は思う。</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p>昔通過した筈なのに、何だか今も囚われているぞ? という感情を思い出させる話。回りくどく自分を騙すことで、自分の苦しいことから自分で目を逸らしているのにそうと自分で気が付かない、という状態、克服したと思ってもやっぱりすぐ陥っちゃいますね。こういうのは自分では「武器」だと思っているわけだから、疑いが向かないわけです。</p>
</div>
<div class="paragraph">
<p>舞城の作品はいつも愛情は尊くて正しい物で、だからか主人公の恋人や配偶者は男も女もみんなかわいくかっこよく愛おしく描かれるよね。</p>
</div>
</div>
<div id="footnotes">
<hr>
<div class="footnote" id="_footnotedef_1">
<a href="#_footnoteref_1">1</a>. 度々やるので舞城王太郎独特のという気にはなるけど、清涼院流水のJDCシリーズに出てくる探偵に由来するんじゃなかろうか。
</div>
<div class="footnote" id="_footnotedef_2">
<a href="#_footnoteref_2">2</a>. なんか単行本で読んだような気でいたけど今検索したら単行本にはなってなくて、短編集『キミトピア』に入っているらしい。この本を読んでいないのは間違いない。ので多分、当時の「新潮」で読んだんだろう。
</div>
<div class="footnote" id="_footnotedef_3">
<a href="#_footnoteref_3">3</a>. というほど実は平和でもないけどね……。
</div>
<div class="footnote" id="_footnotedef_4">
<a href="#_footnoteref_4">4</a>. これが作品の主張だとは言わない。今読んでもちょっとずれてるなとは思う。
</div>
</div>
https://diary.kitaitimakoto.net/2020/02/14.html
みんな元気。 - 小説の思い出
2020-02-14T00:00:00Z
2020-02-14T00:00:00Z
<div class="paragraph">
<p>昨日<a href="../../2019/12/20.html">RubyでのApache Arrowの使い方(Parquetもあるよ)</a>という記事を書いて、Rubyでデータを扱う時に</p>
</div>
<div class="paragraph">
<p>FastestCSV > Arrow > Parquet > CSV</p>
</div>
<div class="paragraph">
<p>の順で速くなる、ということを書いた。これにArrowやRubyのCSVの開発者・メンテナーである<a href="https://github.com/kou/">@kou</a>さんからこんな指摘を貰った。</p>
</div>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">こっちの方が速いと思います。<br>amount = 0<br>Arrow::Table.load(CSVFILE)[4].data.each_chunk do |array|<br> amount += array.cast(Arrow::<a href="https://t.co/S4wh45VLa1">https://t.co/S4wh45VLa1</a>).sum<br>end<br>puts amount<br>が、冗長なのでArrow::Table.load(CSVFILE)[4].cast(:int64).sumくらいで書けるようにしておきます。</p>— す (@ktou) <a href="https://twitter.com/ktou/status/1208286513491898368?ref_src=twsrc%5Etfw">December 21, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<div class="paragraph">
<p><code>each_chunk</code> なんてAPIがあったんだ……! やってみました。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code> user system total real
CSV(Ruby 標準添付CSVライブラリー) 850541456
22.359375 0.390625 22.750000 ( 22.763864)
CSV(FastestCSV RubyGem) 850541456
1.531250 0.140625 1.671875 ( 1.718251)
CSV(Red Arrow each_record_batch) 850541456
10.875000 1.812500 12.687500 ( 10.767980)
CSV(Red Arrow each_record) 850541456
71.437500 1.171875 72.609375 ( 72.159371)
CSV(Red Arrow 対象カラムだけ each) 850541456
3.734375 1.843750 5.578125 ( 3.226278)
CSV(Red Arrow 対象カラムだけ each_chunk) 850541456
0.859375 1.437500 2.296875 ( 0.478962)
Arrow(each_record_batch) 850541456
9.859375 0.078125 9.937500 ( 9.992725)
Arrow(each_record) 850541456
72.781250 0.171875 72.953125 ( 73.775244)
Arrow(対象カラムだけ each) 850541456
2.125000 0.015625 2.140625 ( 2.133398)
Arrow(対象カラムだけ each_chunk) 850541456
0.015625 0.000000 0.015625 ( 0.024413)
Parquet(each_record_batch) 850541456
11.062500 2.078125 13.140625 ( 10.599412)
Parquet(each_record) 850541456
13.343750 0.906250 14.250000 ( 12.923848)
Parquet(対象カラムだけ each) 850541456
3.703125 0.718750 4.421875 ( 3.152494)
Parquet(対象カラムだけ each_chunk) 850541456
0.656250 0.468750 1.125000 ( 0.243396)</code></pre>
</div>
</div>
<div class="paragraph">
<p>見ての通りArrowが圧倒的に速い。</p>
</div>
<div class="paragraph">
<p>Arrow > Parquet > FastestCSV > CSV</p>
</div>
<div class="paragraph">
<p>となってますな。</p>
</div>
<div class="paragraph">
<p>CSVフォーマットを扱うのでも、FastestCSVよりArrowで <code>each_chunk</code> メソッドを使う方が速い。素晴らしい。</p>
</div>
<div class="sect1">
<h2 id="_参考リンク">参考リンク</h2>
<div class="sectionbody">
<div class="ulist">
<ul>
<li>
<p><a href="https://arrow.apache.org/">Apache Arrow公式サイト</a></p>
</li>
<li>
<p><a href="https://github.com/apache/arrow/tree/master/ruby/red-arrow">Red Arrow</a>(ArrowのRubyバインディング)</p>
</li>
<li>
<p><a href="https://github.com/apache/arrow/tree/master/ruby/red-parquet">Red Parquet</a>(Apache ParquetのRubyバインディング)</p>
</li>
<li>
<p><a href="https://docs.ruby-lang.org/ja/latest/library/csv.html">CSV</a></p>
</li>
<li>
<p><a href="https://github.com/brightcode/fastest-csv">FastestCSV</a></p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_ベンチマークスクリプト">ベンチマークスクリプト</h2>
<div class="sectionbody">
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="nb">require</span> <span class="s2">"benchmark"</span>
<span class="nb">require</span> <span class="s2">"csv"</span>
<span class="nb">require</span> <span class="s2">"fastest-csv"</span>
<span class="nb">require</span> <span class="s2">"arrow"</span>
<span class="nb">require</span> <span class="s2">"parquet"</span>
<span class="no">CSVFILE</span> <span class="o">=</span> <span class="s2">"sample-data.csv"</span>
<span class="no">ARROWFILE</span> <span class="o">=</span> <span class="s2">"sample-data.arrow"</span>
<span class="no">PARQUETFILE</span> <span class="o">=</span> <span class="s2">"sample-data.parquet"</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">bmbm</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Ruby 標準添付CSVライブラリー)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">CSV</span><span class="p">.</span><span class="nf">foreach</span> <span class="no">CSVFILE</span><span class="p">,</span> <span class="ss">headers: </span><span class="kp">true</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(FastestCSV RubyGem)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">headers</span> <span class="o">=</span> <span class="kp">true</span>
<span class="no">FastestCSV</span><span class="p">.</span><span class="nf">foreach</span> <span class="no">CSVFILE</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
<span class="k">if</span> <span class="n">headers</span>
<span class="n">headers</span> <span class="o">=</span> <span class="kp">false</span>
<span class="k">next</span>
<span class="k">end</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Red Arrow each_record_batch)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">CSVFILE</span><span class="p">).</span><span class="nf">each_record_batch</span> <span class="k">do</span> <span class="o">|</span><span class="n">records</span><span class="o">|</span>
<span class="n">records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Red Arrow each_record)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">CSVFILE</span><span class="p">).</span><span class="nf">each_record</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Red Arrow 対象カラムだけ each)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">CSVFILE</span><span class="p">).</span><span class="nf">find_column</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">.</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Red Arrow 対象カラムだけ each_chunk)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">CSVFILE</span><span class="p">)[</span><span class="mi">4</span><span class="p">].</span><span class="nf">data</span><span class="p">.</span><span class="nf">each_chunk</span> <span class="k">do</span> <span class="o">|</span><span class="n">array</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">array</span><span class="p">.</span><span class="nf">cast</span><span class="p">(</span><span class="no">Arrow</span><span class="o">::</span><span class="no">Int64DataType</span><span class="p">.</span><span class="nf">new</span><span class="p">).</span><span class="nf">sum</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Arrow(each_record_batch)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">ARROWFILE</span><span class="p">).</span><span class="nf">each_record_batch</span> <span class="k">do</span> <span class="o">|</span><span class="n">records</span><span class="o">|</span>
<span class="n">records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Arrow(each_record)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">ARROWFILE</span><span class="p">).</span><span class="nf">each_record</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Arrow(対象カラムだけ each)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">ARROWFILE</span><span class="p">).</span><span class="nf">find_column</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">.</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Arrow(対象カラムだけ each_chunk)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">ARROWFILE</span><span class="p">)[</span><span class="mi">4</span><span class="p">].</span><span class="nf">data</span><span class="p">.</span><span class="nf">each_chunk</span> <span class="k">do</span> <span class="o">|</span><span class="n">array</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">array</span><span class="p">.</span><span class="nf">cast</span><span class="p">(</span><span class="no">Arrow</span><span class="o">::</span><span class="no">Int64DataType</span><span class="p">.</span><span class="nf">new</span><span class="p">).</span><span class="nf">sum</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Parquet(each_record_batch)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">PARQUETFILE</span><span class="p">).</span><span class="nf">each_record_batch</span> <span class="k">do</span> <span class="o">|</span><span class="n">records</span><span class="o">|</span>
<span class="n">records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Parquet(each_record)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">PARQUETFILE</span><span class="p">).</span><span class="nf">each_record</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Parquet(対象カラムだけ each)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">PARQUETFILE</span><span class="p">).</span><span class="nf">find_column</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">.</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Parquet(対象カラムだけ each_chunk)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">PARQUETFILE</span><span class="p">)[</span><span class="mi">4</span><span class="p">].</span><span class="nf">data</span><span class="p">.</span><span class="nf">each_chunk</span> <span class="k">do</span> <span class="o">|</span><span class="n">array</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">array</span><span class="p">.</span><span class="nf">cast</span><span class="p">(</span><span class="no">Arrow</span><span class="o">::</span><span class="no">Int64DataType</span><span class="p">.</span><span class="nf">new</span><span class="p">).</span><span class="nf">sum</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="k">end</span></code></pre>
</div>
</div>
</div>
</div>
https://diary.kitaitimakoto.net/2019/12/21.html
RubyでのCSV処理はApache Arrowが速い
2019-12-21T00:00:00Z
2019-12-21T00:00:00Z
<div class="paragraph">
<p>Apache Arrow、名前は知ってたけど縁遠いなあと思っていた。最近CSV処理するスクリプト書いてて、そういやデータフォーマットでArrowってあったなっと思って、ちょっと触ってみたのでした。</p>
</div>
<div class="paragraph">
<p>結論、「正しく使えば」、速くなる。</p>
</div>
<div class="sect1">
<h2 id="_apache_arrow">Apache Arrow</h2>
<div class="sectionbody">
<div class="paragraph">
<p>まだよく分かってないんだけど、Apache Arrowは新しいデータフォーマット。</p>
</div>
<div class="ulist">
<ul>
<li>
<p>インメモリーでデータを扱う時に最適化されたフォーマット</p>
</li>
<li>
<p>言語やプロセス間でのデータ交換を高速にするのを目標としている</p>
</li>
<li>
<p>列指向のフォーマット</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>という特徴を持っているらしい。PythonのPandasの開発者も開発の中心的人物みたい。詳しくは公式サイトで: <a href="https://arrow.apache.org/" class="bare">https://arrow.apache.org/</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_arrowでのファイル読み込み">Arrowでのファイル読み込み</h2>
<div class="sectionbody">
<div class="paragraph">
<p>RubyでArrow形式のデータを扱うには<a href="https://github.com/apache/arrow/tree/master/ruby/red-arrow">Red Arrow</a>を使う。これはArrow形式のファイル<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>の他、CSVやParquet形式のファイルも読み込めます。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="nb">require</span> <span class="s2">"arrow"</span>
<span class="n">table</span> <span class="o">=</span> <span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="s2">"sample-data.csv"</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>という感じ。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_csvarrowparquetのベンチマーク">CSV、Arrow、Parquetのベンチマーク</h2>
<div class="sectionbody">
<div class="paragraph">
<p>「列指向」ってことだから(CSVは反対に行指向フォーマット)、一行一行を舐めるような操作は遅いのかな、とか思ったりしつつも、ほんとのところどうなんだろうとベンチマークを取ってみました。</p>
</div>
<div class="sect2">
<h3 id="_環境">環境</h3>
<div class="ulist">
<ul>
<li>
<p>Ubuntu 18.04 on WSL</p>
</li>
<li>
<p>Ruby 2.6.5</p>
</li>
<li>
<p>CSV 3.1.2</p>
</li>
<li>
<p>Red Arrow 0.15.1</p>
</li>
<li>
<p>CPUコア 8個</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>WSLはIOが遅いことで有名なのでその辺は差し引いてください。</p>
</div>
</div>
<div class="sect2">
<h3 id="_対象ファイル">対象ファイル</h3>
<div class="listingblock">
<div class="content">
<pre>% xsv count sample-data.csv
82141
% ruby -rarrow -rparquet -e 't = Arrow::Table.load("sample-data.csv"); t.save("sample-data.arrow"); t.save("sample-data.parquet")'
% ls -lh sample-data.*
Permissions Size User Date Modified Name
.rw-r--r-- 95M kitaitimakoto 20 Dec 18:28 sample-data.arrow
.rw-rw-rw- 61M kitaitimakoto 20 Dec 18:27 sample-data.csv
.rw-r--r-- 33M kitaitimakoto 20 Dec 18:28 sample-data.parquet</pre>
</div>
</div>
<div class="paragraph">
<p>というわけで、</p>
</div>
<div class="ulist">
<ul>
<li>
<p>CSV 61MiB、Arrow 85MiB、Parquet 33MiBのファイルサイズ</p>
</li>
<li>
<p>8万行ちょっとのデータ量</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>のファイルを元にベンチマークを取ってみました。</p>
</div>
<div class="paragraph">
<p>余談だけど、CSVファイルを調べるなら<a href="https://github.com/BurntSushi/xsv">xsv</a>はすごく便利なコマンドなので是非入れよう。</p>
</div>
</div>
<div class="sect2">
<h3 id="_ベンチマーク">ベンチマーク</h3>
<div class="paragraph">
<p>後でスクリプトも置くけど、「ある列(数値)の合計値を計算する」という処理を行っています。</p>
</div>
<div class="ulist">
<ul>
<li>
<p>最初がRuby標準添付の<a href="https://docs.ruby-lang.org/ja/latest/library/csv.html">CSV</a>ライブラリーで処理を行った結果、</p>
</li>
<li>
<p>次が<a href="https://github.com/brightcode/fastest-csv">FastestCSV</a>っていうCで実装したgemでの結果、</p>
</li>
<li>
<p>三番目がRed ArrowのCSV読み込み機能でCSVファイルを読み込んだ時の結果、</p>
</li>
<li>
<p>以降三つがRed Arrowでの各メソッドでの処理結果、</p>
</li>
<li>
<p>以降三つが<a href="https://github.com/apache/arrow/tree/master/ruby/red-parquet">Red Parquest</a>での同様の結果、</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>となっている。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code>% ruby ./arrowbenchmark.rb
(snip)
user system total real
CSV(Ruby 標準添付CSVライブラリー) 850541456
24.625000 0.625000 25.250000 ( 24.844013)
CSV(FastestCSV RubyGem) 850541456
1.343750 0.171875 1.515625 ( 1.507318)
CSV(Red Arrow each_record_batch) 850541456
11.609375 4.328125 15.937500 ( 10.657816)
CSV(Red Arrow each_record) 850541456
68.671875 3.078125 71.750000 ( 67.946809)
CSV(Red Arrow 対象カラムだけ each) 850541456
4.281250 2.781250 7.062500 ( 2.939687)
Arrow(each_record_batch) 850541456
10.390625 0.125000 10.515625 ( 10.410525)
Arrow(each_record) 850541456
68.359375 0.078125 68.437500 ( 68.751072)
Arrow(対象カラムだけ each) 850541456
2.203125 0.000000 2.203125 ( 2.236634)
Parquet(each_record_batch) 850541456
11.125000 1.968750 13.093750 ( 10.353967)
Parquet(each_record) 850541456
12.828125 1.093750 13.921875 ( 12.133342)
Parquet(対象カラムだけ each) 850541456
4.031250 0.781250 4.812500 ( 2.798203)</code></pre>
</div>
</div>
<div class="paragraph">
<p>これ見て分かるのは、</p>
</div>
<div class="ulist">
<ul>
<li>
<p>「正しく使えば」 FastestCSV > Arrow > Parquet > CSV の順で速い</p>
</li>
<li>
<p>Arrowで行操作すると遅い</p>
</li>
<li>
<p>特に <code>each_record</code> はピュアRubyのCSVより遅いぐらいなので必ず <code>each_record_batch</code> 使うこと</p>
</li>
<li>
<p>可能なら対象カラムだけ取り出して( <code>find_column</code> )操作するべし</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>ということかな。</p>
</div>
<div class="paragraph">
<p>追記。「更に正しく使えば」Arrowが圧倒的に速かった。こちらの記事参照:<a href="../../2019/12/21.html">RubyでのCSV処理はApache Arrowが速い</a></p>
</div>
<div class="paragraph">
<p>後掲のスクリプト見ると分かるように、取り出したデータに対して <code>to_i</code> を読んでいる。CSVを読み込んで何もせずArrowやParquet形式で保存しているので、対象カラムが文字列のままだからだ。</p>
</div>
<div class="paragraph">
<p>本当は、ここをuint32とかuint64とかにして保存してやるとさらに高速化できるんだろうなと思いつつ、Arrowのテーブルからそういう別のテーブルを作る方法がいまいち分からないというか、気軽にベンチマークとる上ではめんどそうなので今日はやめました。仕事で使っててどうにもパフォーマンスが欲しいとなったら挑戦するかも。あと何となく時間できた時とか。</p>
</div>
<div class="paragraph">
<p>ベンチマークスクリプトはこちら:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="nb">require</span> <span class="s2">"benchmark"</span>
<span class="nb">require</span> <span class="s2">"csv"</span>
<span class="nb">require</span> <span class="s2">"fastest-csv"</span>
<span class="nb">require</span> <span class="s2">"arrow"</span>
<span class="nb">require</span> <span class="s2">"parquet"</span>
<span class="no">CSVFILE</span> <span class="o">=</span> <span class="s2">"sample-data.csv"</span>
<span class="no">ARROWFILE</span> <span class="o">=</span> <span class="s2">"sample-data.arrow"</span>
<span class="no">PARQUETFILE</span> <span class="o">=</span> <span class="s2">"sample-data.parquet"</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">bmbm</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Ruby 標準添付CSVライブラリー)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">CSV</span><span class="p">.</span><span class="nf">foreach</span> <span class="no">CSVFILE</span><span class="p">,</span> <span class="ss">headers: </span><span class="kp">true</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(FastestCSV RubyGem)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">headers</span> <span class="o">=</span> <span class="kp">true</span>
<span class="no">FastestCSV</span><span class="p">.</span><span class="nf">foreach</span> <span class="no">CSVFILE</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
<span class="k">if</span> <span class="n">headers</span>
<span class="n">headers</span> <span class="o">=</span> <span class="kp">false</span>
<span class="k">next</span>
<span class="k">end</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Red Arrow each_record_batch)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">CSVFILE</span><span class="p">).</span><span class="nf">each_record_batch</span> <span class="k">do</span> <span class="o">|</span><span class="n">records</span><span class="o">|</span>
<span class="n">records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Red Arrow each_record)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">CSVFILE</span><span class="p">).</span><span class="nf">each_record</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"CSV(Red Arrow 対象カラムだけ each)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">CSVFILE</span><span class="p">).</span><span class="nf">find_column</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">.</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Arrow(each_record_batch)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">ARROWFILE</span><span class="p">).</span><span class="nf">each_record_batch</span> <span class="k">do</span> <span class="o">|</span><span class="n">records</span><span class="o">|</span>
<span class="n">records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Arrow(each_record)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">ARROWFILE</span><span class="p">).</span><span class="nf">each_record</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Arrow(対象カラムだけ each)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">ARROWFILE</span><span class="p">).</span><span class="nf">find_column</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">.</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Parquet(each_record_batch)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">PARQUETFILE</span><span class="p">).</span><span class="nf">each_record_batch</span> <span class="k">do</span> <span class="o">|</span><span class="n">records</span><span class="o">|</span>
<span class="n">records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Parquet(each_record)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">PARQUETFILE</span><span class="p">).</span><span class="nf">each_record</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="s2">"Parquet(対象カラムだけ each)"</span> <span class="k">do</span>
<span class="n">amount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">PARQUETFILE</span><span class="p">).</span><span class="nf">find_column</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span>
<span class="n">amount</span> <span class="o">+=</span> <span class="n">record</span><span class="p">.</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">amount</span>
<span class="k">end</span>
<span class="k">end</span></code></pre>
</div>
</div>
</div>
</div>
</div>
<div id="footnotes">
<hr>
<div class="footnote" id="_footnotedef_1">
<a href="#_footnoteref_1">1</a>. Arrowはそのままの形式で(?)ファイルにも保存できるみたい
</div>
</div>
https://diary.kitaitimakoto.net/2019/12/20.html
RubyでのApache Arrowの使い方(Parquetもあるよ)
2019-12-20T00:00:00Z
2019-12-20T00:00:00Z
<div class="paragraph">
<p>ずっと何もしてなかったのだけどなんかフラストレーション溜まってきたのでRustを何でもいいからやろうかあ! って気分になって、<a href="http://www.deqnotes.net/acmicpc/dijkstra/">ダイクストラ法</a>を実装してみようかと思った。</p>
</div>
<div class="paragraph">
<p>取り合えず、Rust 2018のモジュールの仕組みを思い出しながら(<a href="https://keens.github.io/blog/2018/12/08/rustnomoju_runotsukaikata_2018_editionhan/">Rustのモジュールの使い方 2018 Edition版 | κeenのHappy Hacκing Blog</a>)モジュールとしてダイクストラ法をやる場所を作って、それから部品としてのグラフ、ノード、エッジを作ろうと思った。</p>
</div>
<div class="paragraph">
<p>のだけど、それがもううまくいかない。現行のソースコードは <a href="https://gitlab.com/KitaitiMakoto/rust-algorithm/blob/78a47edd7b4d7d8d193b8d5fefd289c95fba4894/src/dijkstra.rs">src/dijkstra.rs</a> なんだけど、 <code>cargo clippy</code> で怒られてしまう。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="rust"><span class="k">struct</span> <span class="n">Edge</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="p">{</span>
<span class="n">dest</span><span class="p">:</span> <span class="o">&</span><span class="nv">'a</span> <span class="n">Node</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span><span class="p">,</span>
<span class="n">cost</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">struct</span> <span class="n">Node</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="p">{</span>
<span class="n">edges</span><span class="p">:</span> <span class="nb">Vec</span><span class="o"><&</span><span class="nv">'a</span> <span class="n">Edge</span><span class="o"><</span><span class="nv">'a</span><span class="o">>></span><span class="p">,</span>
<span class="n">solved</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="n">label</span><span class="p">:</span> <span class="o">&</span><span class="nv">'a</span> <span class="nb">str</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">impl</span> <span class="n">Node</span><span class="o"><</span><span class="nv">'_</span><span class="o">></span> <span class="p">{</span>
<span class="k">fn</span> <span class="n">new</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span><span class="p">(</span><span class="n">label</span><span class="p">:</span> <span class="o">&</span><span class="nv">'a</span> <span class="nb">str</span><span class="p">)</span> <span class="k">-></span> <span class="n">Node</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="p">{</span>
<span class="n">Node</span> <span class="p">{</span>
<span class="n">edges</span><span class="p">:</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
<span class="n">solved</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span>
<span class="n">label</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="n">add_edge_to</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span><span class="p">(</span><span class="o">&</span><span class="k">self</span><span class="p">,</span> <span class="n">dest</span><span class="p">:</span> <span class="o">&</span><span class="nv">'a</span> <span class="n">Node</span><span class="p">,</span> <span class="n">cost</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">edge</span> <span class="o">=</span> <span class="o">&</span><span class="n">Edge</span> <span class="p">{</span><span class="n">dest</span><span class="p">,</span> <span class="n">cost</span><span class="p">};</span>
<span class="k">self</span><span class="py">.edges</span><span class="nf">.push</span><span class="p">(</span><span class="o">&</span><span class="n">edge</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">struct</span> <span class="n">Graph</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="p">{</span>
<span class="n">nodes</span><span class="p">:</span> <span class="nb">Vec</span><span class="o"><&</span><span class="nv">'a</span> <span class="n">Node</span><span class="o"><</span><span class="nv">'a</span><span class="o">>></span><span class="p">,</span>
<span class="p">}</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">run</span><span class="p">()</span> <span class="k">-></span> <span class="nb">Result</span><span class="o"><</span><span class="p">(),</span> <span class="p">()</span><span class="o">></span> <span class="p">{</span>
<span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span></code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code>% cargo clippy
Checking rust-algorithm v0.1.0 (/home/kitaitimakoto/src/gitlab.com/KitaitiMakoto/rust-algorithm)
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
--> src/dijkstra.rs:22:21
|
22 | let edge = &Edge {dest, cost};
| ^^^^
|
note: first, the lifetime cannot outlive the lifetime 'a as defined on the method body at 21:20...
--> src/dijkstra.rs:21:20
|
21 | fn add_edge_to<'a>(&self, dest: &'a Node, cost: usize) {
| ^^
note: ...so that reference does not outlive borrowed content
--> src/dijkstra.rs:22:27
|
22 | let edge = &Edge {dest, cost};
| ^^^^
note: but, the lifetime must be valid for the lifetime '_ as defined on the impl at 12:11...
--> src/dijkstra.rs:12:11
|
12 | impl Node<'_> {
| ^^
note: ...so that reference does not outlive borrowed content
--> src/dijkstra.rs:23:25
|
23 | self.edges.push(&edge);
| ^^^^^
error: aborting due to previous error
error: could not compile `rust-algorithm`.
To learn more, run the command again with --verbose.</code></pre>
</div>
</div>
<div class="paragraph">
<p>こういう所でハマるのは、まさにそのために勉強しているわけなので歓迎なんだけど、それにしても分からん。なんでなんだろう……。</p>
</div>
<div class="paragraph">
<p>何か教えてくださるという親切な方がいらしたらこちらまでお願いします…… <a href="https://gitlab.com/KitaitiMakoto/rust-algorithm/issues" class="bare">https://gitlab.com/KitaitiMakoto/rust-algorithm/issues</a><br>
あと、実はこっそりSlackの<a href="https://rust-jp.slack.com">rust-jpチーム</a>にもいます。</p>
</div>
https://diary.kitaitimakoto.net/2019/12/08.html
Rustのライフタイムが(やっぱり)分からない
2019-12-08T00:00:00Z
2019-12-08T00:00:00Z
<div class="paragraph">
<p><a href="https://kitaitimakoto.gitlab.io/epub-parser/file.Home.html">EPUB Parser</a> 0.4.1をリリースした( <a href="https://kitaitimakoto.gitlab.io/epub-parser/file.CHANGELOG.html#_0_4_1" class="bare">https://kitaitimakoto.gitlab.io/epub-parser/file.CHANGELOG.html#_0_4_1</a> )。XMLライブラリーを切り替え可能にしているのだけど、そこで<a href="https://gitlab.com/yorickpeterse/oga">Oga</a>も使えるようにしたというリリース。</p>
</div>
<div class="paragraph">
<p>Ruby 2.7の足音も近付いている昨今ですが、このOga対応中に2.6の<a href="https://magazine.rubyist.net/articles/0041/0041-200Special-refinement.html">Refinements</a>の使いやすさをすごく実感したので今日はそのことを話したい。</p>
</div>
<div class="sect1">
<h2 id="_refinementの用途_ライブラリー間の差を吸収するアダプター">Refinementの用途 - ライブラリー間の差を吸収するアダプター</h2>
<div class="sectionbody">
<div class="paragraph">
<p>EPUB ParserではXMLライブラリーを<a href="https://docs.ruby-lang.org/ja/2.6.0/library/rexml.html">REXML</a>、<a href="https://gitlab.com/yorickpeterse/oga">Oga</a>、<a href="https://nokogiri.org/">Nokogiri</a>から選べるようになっている。こういうのの実現は、多く、 <code>XMLDocument::REXML</code> のようなアダプターを作ってその中にライブラリーの詳細を隠蔽する。EPUB Parserの方からは共通の <code>XMLDocument</code> のAPIを使って、内部でアダプターがREXMLならREXMLのメソッド呼び出しに変換する、という方法だ。</p>
</div>
<div class="paragraph">
<p>が、EPUB Parserではそういうラッパーは用意しなかった。直接 <code>REXML::Document</code> とか <code>Oga::XML::Document</code> とかのメソッドを呼び出している。とは言っても、勿論、一々</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">doc</span> <span class="o">=</span>
<span class="k">case</span> <span class="vi">@adapter</span>
<span class="k">when</span> <span class="ss">:Oga</span>
<span class="no">Oga</span><span class="p">.</span><span class="nf">parse_xml</span><span class="p">(</span><span class="n">xml</span><span class="p">)</span>
<span class="k">when</span> <span class="ss">:Nokogiri</span>
<span class="no">Nokogiri</span><span class="o">.</span><span class="no">XML</span><span class="p">(</span><span class="n">xml</span><span class="p">)</span>
<span class="k">else</span>
<span class="no">REXML</span><span class="o">::</span><span class="no">Document</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">xml</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">xpath</span> <span class="o">=</span> <span class="s2">"/container/rootfiles/rootfile"</span>
<span class="n">rootfiles</span> <span class="o">=</span>
<span class="k">case</span> <span class="vi">@adapter</span>
<span class="k">when</span> <span class="ss">:Oga</span>
<span class="n">doc</span><span class="p">.</span><span class="nf">xpath</span><span class="p">(</span><span class="n">xpath</span><span class="p">)</span>
<span class="k">when</span> <span class="ss">:Nokogiri</span>
<span class="n">doc</span><span class="p">.</span><span class="nf">xpath</span><span class="p">(</span><span class="n">xpath</span><span class="p">)</span>
<span class="k">else</span>
<span class="n">doc</span><span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="n">xpath</span><span class="p">)</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>などと条件分岐するわけではない。その代わりに用いたのがRefinement、というわけだ。</p>
</div>
<div class="paragraph">
<p>例えばEPUB Parser内でXPath式に基づいて要素を取得する場合には <code>each_element_by_xpath</code> というメソッドを呼び出すことにしているが、ご存じの通りどのXMLライブラリーにもこういう名前のメソッドは備わっていない。だから各ライブラリーのクラスにこのメソッドを新しく定義するのだけど、それをオープンクラスでやったり <code><a href="https://docs.ruby-lang.org/ja/2.6.0/method/Module/i/prepend.html">prepend</a></code> 、 <code><a href="https://docs.ruby-lang.org/ja/2.6.0/method/Module/i/include.html">include</a></code> でやると、EPUB Parserで読み込んだ状態では各XMLライブラリーに未知のメソッドが生えることになってしまう。EPUB Parserを使ってHTMLコンテンツを読み込んで、それを自分の好きなXMLライブラリーでパースすることも割とあると思うのだけど、その時に意図しないメソッドが生えているのは嫌だ。そこで活躍するのがRefinement。 <code>prepend</code> みたいにメソッドを定義したモジュールを読み込ませるんだけど、その読み込みは明示的に <code>using</code> した範囲に限られる。EPUB Parserの内部処理で <code>using EPUB::Parser::XMLDocuemnt::Refinements</code> とかして、そのモジュール内で <code>each_element_by_xpath</code> とか定義してても、ユーザーが自分のXMLライブラリーを使う時にはそんなメソッドは無くなっている。</p>
</div>
<div class="paragraph">
<p>REXMLとNokogiriの両方を使えるようにする際にこういう仕組みを導入した。今回、0.4.1のリリースでOga対応する時にもこれに則って必要な書くライブラリーを定義していった。特に困ることなく普通にRefinementを使って実装した後GitLabにプッシュするとCIが動く。そしてテストの失敗を報告してくる。手元の開発はRuby 2.6だけでやっていて、2.6用のCIでは当然成功しているんだけど、2.3 - 2.5では失敗していて、これを修正する間に「ああ、Ruby 2.6のRefinementは過去のバージョンと比べて自然に使えるようになってるんだなあ、便利だなあ」と実感した次第だ。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_モジュールをrefineできるようになっている">モジュールをrefineできるようになっている。</h2>
<div class="sectionbody">
<div class="paragraph">
<p>2.4、2.5と違い、2.3のCIではテストを開始する以前にエラーが発生している。Ruby 2.3のRefinementは、どうやらクラスしか <code>refine</code> できず、 <code>refine Oga::XML::Traversal</code> でエラーになっていた。</p>
</div>
<div class="paragraph">
<p><a href="https://gitlab.com/KitaitiMakoto/epub-parser/-/jobs/365693836" class="bare">https://gitlab.com/KitaitiMakoto/epub-parser/-/jobs/365693836</a></p>
</div>
<div class="paragraph">
<p>今ではモジュールも <code>refine</code> できて便利、と言うか、開発中はできることを疑わなかった。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_シンボルをブロック化する時にrefinementが適用される">シンボルをブロック化する時にRefinementが適用される</h2>
<div class="sectionbody">
<div class="paragraph">
<p>見出しだけだと何を言っているか分からないと思う。僕も分からない。こういうことだ。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="p">[</span><span class="o">::</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">,</span> <span class="o">::</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Node</span><span class="p">].</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">klass</span><span class="o">|</span>
<span class="n">refine</span> <span class="n">klass</span> <span class="k">do</span>
<span class="p">[</span>
<span class="p">[</span><span class="ss">:document</span><span class="p">,</span> <span class="o">::</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:element</span><span class="p">,</span> <span class="o">::</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Element</span><span class="p">],</span>
<span class="p">[</span><span class="ss">:text</span><span class="p">,</span> <span class="o">::</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Text</span><span class="p">]</span>
<span class="p">].</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="n">klass</span><span class="p">)</span><span class="o">|</span>
<span class="n">define_method</span> <span class="s2">"</span><span class="si">#{</span><span class="n">type</span><span class="si">}</span><span class="s2">?"</span> <span class="k">do</span>
<span class="nb">kind_of?</span> <span class="n">klass</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>こうすると、 <code>using</code> すると <code>Oga.parse_xml(xml).element?</code> とかができるようになる。のだけど、Ruby 2.6未満では次のメソッド呼び出しが失敗してしまっていた。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">root</span>
<span class="n">root_node</span><span class="p">.</span><span class="nf">children</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="o">&</span><span class="ss">:element?</span><span class="p">)</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>「<code>children</code> がイテレートするXMLノードに <code>element?</code> なんてメソッドは無い」と言われてしまう。定義しているのになんで? 次のようにするとちゃんと <code>element?</code> メソッドでフィルタリングできる。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">root</span>
<span class="n">root_node</span><span class="p">.</span><span class="nf">children</span><span class="p">.</span><span class="nf">find</span> <span class="p">{</span><span class="o">|</span><span class="n">child</span><span class="o">|</span> <span class="n">child</span><span class="p">.</span><span class="nf">element?</span><span class="p">}</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>こういうことができるのか調べようという発想すらなく、自然と</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">root_node</span><span class="p">.</span><span class="nf">children</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="o">&</span><span class="ss">:element?</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>って書いていたので、この時にRefinementが適用されるのはRubyistにとって自然なことなんだと思う<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>。2.6はすごく自然だ。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_respond_toでもrefinementが考慮される">respond_to?でもRefinementが考慮される</h2>
<div class="sectionbody">
<div class="paragraph">
<p>上の例で引き続き、</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">node</span><span class="p">.</span><span class="nf">respond_to?</span> <span class="ss">:root</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>と <code>respond_to?</code> を呼び出すと、2.5までは <code>false</code> が返って来てしまう。2.6はちゃん<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup>と <code>true</code> が返って来る。素晴らしい。</p>
</div>
<hr>
<div class="paragraph">
<p>Refinementはリリースノートをざっと見るだけだとどういう意味を持つのか分からない改善とかあるけど、Ruby 2.6ではRefinementがより自然に使えるようになっていたんだなあ。2.7ではもっとよくなっているのか知ら。楽しみですね。</p>
</div>
</div>
</div>
<div id="footnotes">
<hr>
<div class="footnote" id="_footnotedef_1">
<a href="#_footnoteref_1">1</a>. 主語がでかい。僕にとっては、です。
</div>
<div class="footnote" id="_footnotedef_2">
<a href="#_footnoteref_2">2</a>. 僕にとって、ちゃんと。
</div>
</div>
https://diary.kitaitimakoto.net/2019/12/06.html
Ruby 2.6のRefinementsが使いやすい
2019-12-06T00:00:00Z
2019-12-06T00:00:00Z
<div class="paragraph">
<p><a href="../../2019/11/23.html">雑多にプログラミング、集中力の衰え</a>で書いたようにRubyのXMLライブラリー<a href="https://gitlab.com/yorickpeterse/oga">Oga</a>にパッチを投げていたんだけど、製作者の<a href="https://yorickpeterse.com/">Yorick Peterse</a>さんとの幾つかのやり取りの上、マージされた:<a href="https://gitlab.com/yorickpeterse/oga/issues/176">Improve XPath namespace support</a>。やったあ、と思ってたら、<a href="https://rubygems.org/gems/oga/versions/2.16">バージョン2.16</a>としてリリースされた。早い。</p>
</div>
<div class="sect1">
<h2 id="_rubyコードへのコンパイル">Rubyコードへのコンパイル</h2>
<div class="sectionbody">
<div class="paragraph">
<p>OgaのXPath実装は面白くて、</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>XPathをパースしてXPathのASTを作る(中ではトークナイズとパースがあるけどここではまとめてパースと呼ぶ)</p>
</li>
<li>
<p>XPathのASTを元に <strong>RubyのASTを作る</strong></p>
</li>
<li>
<p>RubyのASTを元にRubyコードの文字列を作る</p>
</li>
<li>
<p>Rubyコードを評価(ざっくり言うと <code>eval</code>)して実行する</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>となっている。えっ、Rubyコードを作るの!? とびっくりしたけど、まじで作ってる。</p>
</div>
<div class="paragraph">
<p>OgaではXPathを使う時にはこうやって使う。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="nb">require</span> <span class="s2">"oga"</span>
<span class="n">xml</span> <span class="o">=</span> <span class="no">DATA</span><span class="p">.</span><span class="nf">read</span>
<span class="n">doc</span> <span class="o">=</span> <span class="no">Oga</span><span class="p">.</span><span class="nf">parse_xml</span><span class="p">(</span><span class="n">xml</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">doc</span><span class="p">.</span><span class="nf">xpath</span><span class="p">(</span><span class="s2">"//foo[@bar]"</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nf">to_xml</span>
<span class="c1"># <foo bar="baz"></span>
<span class="c1"># <qux /></span>
<span class="c1"># </foo></span>
<span class="cp">__END__
<root>
<foo bar="baz">
<qux />
</foo>
</root>
</span></code></pre>
</div>
</div>
<div class="paragraph">
<p><code>//foo[@bar]</code> というXPath式は「ドキュメント中のどこにあってもいいから <code>foo</code> という要素名で、 <code>bar</code> という属性を持っている(値は問わない)要素全部」を意味する。CSSセレクターだと <code>foo[bar]</code> に相当する。</p>
</div>
<div class="paragraph">
<p>この <code>doc.xpath</code> の所で、上に書いたステップを踏んでいる。</p>
</div>
<div class="paragraph">
<p>まず、与えられたXPath式をパースする:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">ast</span> <span class="o">=</span> <span class="no">Oga</span><span class="o">::</span><span class="no">XPath</span><span class="o">::</span><span class="no">Parser</span><span class="p">.</span><span class="nf">parse_with_cache</span><span class="p">(</span><span class="s2">"//foo[@bar]"</span><span class="p">)</span>
<span class="n">pp</span> <span class="n">ast</span>
<span class="c1"># s(:absolute_path,</span>
<span class="c1"># s(:axis, "descendant-or-self",</span>
<span class="c1"># s(:type_test, "node"),</span>
<span class="c1"># s(:predicate,</span>
<span class="c1"># s(:axis, "child",</span>
<span class="c1"># s(:test, nil, "foo")),</span>
<span class="c1"># s(:axis, "attribute",</span>
<span class="c1"># s(:test, nil, "bar")))))</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>これは抽象構文木になっていて、具体的にはこうなってる。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://i.gyazo.com/dc59592524cf10369330e071808fba80.png" alt="dc59592524cf10369330e071808fba80">
</div>
</div>
<div class="paragraph">
<p><code>absolute_path</code> はXPathの最初の <code>/</code> で、次の <code>/</code> が <code>axis</code> になる。その <code>axis</code> の子ノードが三つあってそれぞれ文字列 <code>"descendant-or-self"</code> と <code>type_test</code> と <code>predicate</code> ……という風に読んでいく。</p>
</div>
<div class="paragraph">
<p>そしてこれを今度は、RubyのASTに変換する。これは <code>Oga::XPath::Compiler.compile_with_cache</code> メソッドの中で行われるんだけど、ASTを組み立てる独立したメソッドがないので、メソッドに中に <code>print</code> を仕込んだ結果がこう。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code>(block (lit "lambda") [(lit "node"), (assign (lit "variables") (lit "nil"))] (followed_by (assign (lit "original_input") (lit "node")) (followed_by (followed_by (assign (lit "matched") (send (lit "Oga::XML::NodeSet") "new")) (if (or (send (send (lit "node") "root_node") "is_a?" (lit "Oga::XML::Document")) (send (send (lit "node") "root_node") "is_a?" (lit "Oga::XML::Node"))) (followed_by (if (or (or (send (send (lit "node") "root_node") "is_a?" (lit "Oga::XML::Document")) (send (send (lit "node") "root_node") "is_a?" (lit "Oga::XML::Node"))) (send (send (lit "node") "root_node") "is_a?" (lit "Oga::XML::Attribute"))) (followed_by (assign (lit "index2") (lit "1")) (if (or (send (send (lit "node") "root_node") "is_a?" (lit "Oga::XML::Document")) (send (send (lit "node") "root_node") "is_a?" (lit "Oga::XML::Node"))) (block (send (send (send (lit "node") "root_node") "children") "each") [(lit "child4")] (if (and (or (send (lit "child4") "is_a?" (lit "Oga::XML::Element")) (send (lit "child4") "is_a?" (lit "Oga::XML::Attribute"))) (or (eq (send (lit "child4") "name") (string "foo")) (eq (send (send (lit "child4") "name") "casecmp" (string "foo")) (lit "0")))) (followed_by (followed_by (followed_by (assign (lit "pred_var3") (block (send nil "catch" (symbol :predicate_matched)) [] (followed_by (if (send (lit "child4") "is_a?" (lit "Oga::XML::Element")) (block (send (send (lit "child4") "attributes") "each") [(lit "attribute5")] (if (or (eq (send (lit "attribute5") "name") (string "bar")) (eq (send (send (lit "attribute5") "name") "casecmp" (string "bar")) (lit "0"))) (send nil "throw" (symbol :predicate_matched) (lit "true"))))) (lit "nil")))) (if (send (lit "pred_var3") "is_a?" (lit "Numeric")) (assign (lit "pred_var3") (eq (send (lit "pred_var3") "to_i") (lit "index2"))))) (if (send (lit "Oga::XPath::Conversion") "to_boolean" (lit "pred_var3")) (send (lit "matched") "push" (lit "child4")))) (assign (lit "index2") (send (lit "index2") "+" (lit "1"))))))))) (block (send (send (lit "node") "root_node") "each_node") [(lit "descendant1")] (if (or (or (send (lit "descendant1") "is_a?" (lit "Oga::XML::Document")) (send (lit "descendant1") "is_a?" (lit "Oga::XML::Node"))) (send (lit "descendant1") "is_a?" (lit "Oga::XML::Attribute"))) (followed_by (assign (lit "index6") (lit "1")) (if (or (send (lit "descendant1") "is_a?" (lit "Oga::XML::Document")) (send (lit "descendant1") "is_a?" (lit "Oga::XML::Node"))) (block (send (send (lit "descendant1") "children") "each") [(lit "child8")] (if (and (or (send (lit "child8") "is_a?" (lit "Oga::XML::Element")) (send (lit "child8") "is_a?" (lit "Oga::XML::Attribute"))) (or (eq (send (lit "child8") "name") (string "foo")) (eq (send (send (lit "child8") "name") "casecmp" (string "foo")) (lit "0")))) (followed_by (followed_by (followed_by (assign (lit "pred_var7") (block (send nil "catch" (symbol :predicate_matched)) [] (followed_by (if (send (lit "child8") "is_a?" (lit "Oga::XML::Element")) (block (send (send (lit "child8") "attributes") "each") [(lit "attribute9")] (if (or (eq (send (lit "attribute9") "name") (string "bar")) (eq (send (send (lit "attribute9") "name") "casecmp" (string "bar")) (lit "0"))) (send nil "throw" (symbol :predicate_matched) (lit "true"))))) (lit "nil")))) (if (send (lit "pred_var7") "is_a?" (lit "Numeric")) (assign (lit "pred_var7") (eq (send (lit "pred_var7") "to_i") (lit "index6"))))) (if (send (lit "Oga::XPath::Conversion") "to_boolean" (lit "pred_var7")) (send (lit "matched") "push" (lit "child8")))) (assign (lit "index6") (send (lit "index6") "+" (lit "1"))))))))))))) (lit "matched"))))</code></pre>
</div>
</div>
<div class="paragraph">
<p>……これを図にしても読めないと思うのでしないけど、さっきのASTが読めれば「RubyのコードがASTとして表現されてるんだなー」という雰囲気は伝わると思う。あると便利かなと思う知識は、 <code>(lit "lambda")</code> みたいな <code>lit</code> ノードは、Rubyに於けるリテラル、つまりRubyコードとしてはベタで <code>lambda</code> と書かれる物。あと、 <code>(lit "node")</code> とか <code>(list "child4")</code> とかはOgaが用意する変数で、 <code>Oga::XML::Node</code> が入ってる。因みにこのASTを組み立てるところが、パッチを書く上で一番苦労した。</p>
</div>
<div class="paragraph">
<p>まあそもそもこれを頑張って細部まで読む必要がなくて、このASTを組み立てた直後にRubyコードの文字列にする処理があるので、その結果を貼り付けよう(インデントの調整だけ僕がしてある)。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="nb">lambda</span> <span class="k">do</span> <span class="o">|</span><span class="n">node</span><span class="p">,</span> <span class="n">variables</span> <span class="o">=</span> <span class="kp">nil</span><span class="o">|</span>
<span class="n">original_input</span> <span class="o">=</span> <span class="n">node</span>
<span class="n">matched</span> <span class="o">=</span> <span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">NodeSet</span><span class="p">.</span><span class="nf">new</span>
<span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">)</span> <span class="o">||</span> <span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Node</span><span class="p">))</span>
<span class="k">if</span> <span class="p">((</span><span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">)</span> <span class="o">||</span> <span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Node</span><span class="p">))</span> <span class="o">||</span> <span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Attribute</span><span class="p">))</span>
<span class="n">index2</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">)</span> <span class="o">||</span> <span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Node</span><span class="p">))</span>
<span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">children</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">child4</span><span class="o">|</span>
<span class="k">if</span> <span class="p">(</span><span class="n">child4</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Element</span><span class="p">)</span> <span class="o">||</span> <span class="n">child4</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Attribute</span><span class="p">))</span> <span class="o">&&</span> <span class="p">(</span><span class="n">child4</span><span class="p">.</span><span class="nf">name</span> <span class="o">==</span> <span class="s2">"foo"</span> <span class="o">||</span> <span class="n">child4</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">casecmp</span><span class="p">(</span><span class="s2">"foo"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">pred_var3</span> <span class="o">=</span> <span class="kp">catch</span><span class="p">(</span><span class="ss">:predicate_matched</span><span class="p">)</span> <span class="k">do</span> <span class="o">||</span>
<span class="k">if</span> <span class="n">child4</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Element</span><span class="p">)</span>
<span class="n">child4</span><span class="p">.</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">attribute5</span><span class="o">|</span>
<span class="k">if</span> <span class="p">(</span><span class="n">attribute5</span><span class="p">.</span><span class="nf">name</span> <span class="o">==</span> <span class="s2">"bar"</span> <span class="o">||</span> <span class="n">attribute5</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">casecmp</span><span class="p">(</span><span class="s2">"bar"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="kp">throw</span><span class="p">(</span><span class="ss">:predicate_matched</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="kp">nil</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">pred_var3</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Numeric</span><span class="p">)</span>
<span class="n">pred_var3</span> <span class="o">=</span> <span class="n">pred_var3</span><span class="p">.</span><span class="nf">to_i</span> <span class="o">==</span> <span class="n">index2</span>
<span class="k">end</span>
<span class="k">if</span> <span class="no">Oga</span><span class="o">::</span><span class="no">XPath</span><span class="o">::</span><span class="no">Conversion</span><span class="p">.</span><span class="nf">to_boolean</span><span class="p">(</span><span class="n">pred_var3</span><span class="p">)</span>
<span class="n">matched</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">child4</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">index2</span> <span class="o">=</span> <span class="n">index2</span><span class="p">.</span><span class="nf">+</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">node</span><span class="p">.</span><span class="nf">root_node</span><span class="p">.</span><span class="nf">each_node</span> <span class="k">do</span> <span class="o">|</span><span class="n">descendant1</span><span class="o">|</span>
<span class="k">if</span> <span class="p">((</span><span class="n">descendant1</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">)</span> <span class="o">||</span> <span class="n">descendant1</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Node</span><span class="p">))</span> <span class="o">||</span> <span class="n">descendant1</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Attribute</span><span class="p">))</span>
<span class="n">index6</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="p">(</span><span class="n">descendant1</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">)</span> <span class="o">||</span> <span class="n">descendant1</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Node</span><span class="p">))</span>
<span class="n">descendant1</span><span class="p">.</span><span class="nf">children</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">child8</span><span class="o">|</span>
<span class="k">if</span> <span class="p">(</span><span class="n">child8</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Element</span><span class="p">)</span> <span class="o">||</span> <span class="n">child8</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Attribute</span><span class="p">))</span> <span class="o">&&</span> <span class="p">(</span><span class="n">child8</span><span class="p">.</span><span class="nf">name</span> <span class="o">==</span> <span class="s2">"foo"</span> <span class="o">||</span> <span class="n">child8</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">casecmp</span><span class="p">(</span><span class="s2">"foo"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">pred_var7</span> <span class="o">=</span> <span class="kp">catch</span><span class="p">(</span><span class="ss">:predicate_matched</span><span class="p">)</span> <span class="k">do</span> <span class="o">||</span>
<span class="k">if</span> <span class="n">child8</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Oga</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Element</span><span class="p">)</span>
<span class="n">child8</span><span class="p">.</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">attribute9</span><span class="o">|</span>
<span class="k">if</span> <span class="p">(</span><span class="n">attribute9</span><span class="p">.</span><span class="nf">name</span> <span class="o">==</span> <span class="s2">"bar"</span> <span class="o">||</span> <span class="n">attribute9</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">casecmp</span><span class="p">(</span><span class="s2">"bar"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="kp">throw</span><span class="p">(</span><span class="ss">:predicate_matched</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="kp">nil</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">pred_var7</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Numeric</span><span class="p">)</span>
<span class="n">pred_var7</span> <span class="o">=</span> <span class="n">pred_var7</span><span class="p">.</span><span class="nf">to_i</span> <span class="o">==</span> <span class="n">index6</span>
<span class="k">end</span>
<span class="k">if</span> <span class="no">Oga</span><span class="o">::</span><span class="no">XPath</span><span class="o">::</span><span class="no">Conversion</span><span class="p">.</span><span class="nf">to_boolean</span><span class="p">(</span><span class="n">pred_var7</span><span class="p">)</span>
<span class="n">matched</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">child8</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">index6</span> <span class="o">=</span> <span class="n">index6</span><span class="p">.</span><span class="nf">+</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">matched</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>最後にこれをRubyのブロックでラップして、XMLドキュメントの文脈で実行したら結果が返ってくるという次第。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">block</span> <span class="o">=</span> <span class="no">Oga</span><span class="o">::</span><span class="no">XPath</span><span class="o">::</span><span class="no">Compiler</span><span class="p">.</span><span class="nf">compile_with_cache</span><span class="p">(</span><span class="n">ast</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">block</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">doc</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nf">to_xml</span>
<span class="c1"># <foo bar="baz"></span>
<span class="c1"># <qux /></span>
<span class="c1"># </foo></span></code></pre>
</div>
</div>
<div class="paragraph">
<p>実行時にXMLドキュメントのDOMツリーを辿りながら、並行してXPath式のASTを辿る(インタープリター型)のかなあと思い込んでいたんだけど、いったん文字列にしてそれを評価するとは……確かに「コンパイル」だな、と驚いたし興奮した。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_新たな問題">新たな問題</h2>
<div class="sectionbody">
<div class="paragraph">
<p>書いたパッチはこの <code>xpath</code> メソッドにキーワード引数 <code>namespaces</code> を足して、XMLのパース時ではなくてXPathでクエリーする時に任意の名前空間を使えるようにする、という物で、その追加について<a href="https://gitlab.com/yorickpeterse/oga/blob/d492a775bf4784b4a6532a60cf0228cf40c8616c/README.md">README</a>にコメントを書こうと思ったら新たな問題を掘り起こしてしまった。それがこちら:<a href="https://gitlab.com/yorickpeterse/oga/issues/195">XPath queries using the default XML namespace do not appear to work (any more)</a></p>
</div>
<div class="paragraph">
<p>XPathの扱いが、なんと四年も前から間違っていたとのこと……まじか。<a href="https://gitlab.com/KitaitiMakoto/epub-parser">EPUB Parser</a>でOgaを使えるようにしたいと思っていたのだけど、このバグあると取り入れられないので、この問題も引き続き調べていくことにしよう。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_yorick_peterse">Yorick Peterse</h2>
<div class="sectionbody">
<div class="paragraph">
<p>余談だけどYorickさん、<a href="http://rubini.us/">Rubinius</a>とか<a href="https://github.com/pry/pry">Pry</a>とかの作者だったのね。個人的に好きだったウェブアプリケーションフレームワーク<a href="http://ramaze.net/">Ramaze</a>の作者でもある(これは知ってた)。パーサージェネレーターを作ったりもしてる。そう思うと「RubyコードをRubyコードで扱う」っていうアプローチも慣れたもんなんだろうな。</p>
</div>
</div>
</div>
https://diary.kitaitimakoto.net/2019/11/29.html
Ogaへのパッチが取り込まれた、OgaはXPath評価時にRubyコードへコンパイルしていて興奮した
2019-11-29T00:00:00Z
2019-11-29T00:00:00Z
<div class="paragraph">
<p>風邪で寝込みながらポッドキャストを聞いていた。熱が出ると画面見るの辛いし、端末を持ち続けるのも辛いので音声メディアはとてもいい。</p>
</div>
<div class="sect1">
<h2 id="_23_社内isucon_w_yosuke_furukawa_fukabori_fm"><a href="https://fukabori.fm/episode/23">23. 社内ISUCON w/ yosuke_furukawa | Fukabori.fm</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://twitter.com/yosuke_furukawa">@yosuke_furukawa</a>さんと、社内ISUCONの話。</p>
</div>
<div class="paragraph">
<p>ベンチマーカーを作るのに、オープンソースの<a href="https://github.com/loadimpact/k6">k6</a>と<a href="https://www.getpostman.com/">Postman</a>を元にして作っている、と言っていて、「これぞエンジニアリング!」と興奮した(けどこれは別にエンジニアリングかどうかは関係ない。どういう言葉を僕は本当は使いたかったんだろう?)。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_13_ペアプロやテストの疑問とかソフトウェアエンジニアの育成とか_fukabori_fm"><a href="https://fukabori.fm/episode/13">13. ペアプロやテストの疑問とか、ソフトウェアエンジニアの育成とか | Fukabori.fm</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://twitter.com/t_wada">twada</a>さんと、ペアプロやテストの話。</p>
</div>
<div class="paragraph">
<p>ペアプロをベテラン×ベテランの組み合わせでやる時のメリットの話が、自分で実際やらずには考えられない内容だったのでためになった。アーキテクチャーみたいな部分での決定について、ペアプロでやっていると共犯関係を作れるとか、自分にない観点での議論ができるとか。なるほどなあ。</p>
</div>
<div class="paragraph">
<p>あと、ペアプロやってると、「他人に見られるというただそれだけで雑なコードを書かなくなる」「これはコードレビューも同じ」みたいな話も納得感あった。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_15_良い組織とは何かどのように良い組織を作っていくのか_fukabori_fm"><a href="https://fukabori.fm/episode/15">15. 良い組織とは何か?どのように良い組織を作っていくのか? | Fukabori.fm</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://speakerdeck.com/320kz/sutatoatupuzu-zhi-kai-fa-mian-qiang-hui-scoutyfalsezu-zhi-kai-fa-li-lun-toshi-jian">スタートアップ組織開発勉強会_scoutyの組織開発理論と実践</a>というプレゼン資料を元に話す回。</p>
</div>
<script async class="speakerdeck-embed" data-id="aedfdf3675c5408fa0fe8dfdb9b5155e" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>
<div class="paragraph">
<p>とてもいい話だった。クレブスサイクルというのを知れたのが僕には大きい。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_14_なぜエンタープライズ業界でアジャイルリーンは普及しないのか_fukabori_fm"><a href="https://fukabori.fm/episode/14">14. なぜ、エンタープライズ業界でアジャイル・リーンは普及しないのか? | Fukabori.fm</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://twitter.com/hiranabe">hiranabe</a>さんと、アジャイルとかの話。</p>
</div>
<div class="paragraph">
<p>何か事業やプロジェクトが失敗した際に、「企画はよかったのだが、実施がうまくいかなかったのか」「それとも企画がそもそもよくなかったのか」という振り返り方をするのだけど、それはよくないよ、という話がのっけから行われていて、引き込まれた。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_voicyをポッドキャストアプリで聴きたい">Voicyをポッドキャストアプリで聴きたい</h2>
<div class="sectionbody">
<div class="paragraph">
<p>風邪の時とかそうでなくても寝ながらポッドキャスト聴くのはとても好き。</p>
</div>
<div class="paragraph">
<p>最近聴いて面白かった音声メディアに<a href="https://twitter.com/ciotan">塩谷舞</a>さんのVoicy「<a href="https://voicy.jp/channel/619">塩谷舞の東京とNYと大阪と。</a>」があるのだけど、これも自分の好きなポッドキャストアプリでサブスクライブして、他のポッドキャストと同じ場所に並んで、ポッドキャストのように聴けたらいいのになあと思う。</p>
</div>
</div>
</div>
https://diary.kitaitimakoto.net/2019/11/28.html
風邪で寝込みながらfukabori.fm聴いていた
2019-11-28T00:00:00Z
2019-11-28T00:00:00Z
<div class="sect1">
<h2 id="_ogaのxpathの名前空間サポートの拡充">OgaのXPathの名前空間サポートの拡充</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="http://kitaitimakoto.gitlab.io/epub-parser/">EPUB Parser</a>というRubyGemを作っている。電子書籍用に使われるEPUBというファイルフォーマットを扱うライブラリーなのだけど、EPUBというのは雑に言って「XHTMLとCSSと画像とメタデータ用XMLファイルをZIPアーカイブした物」なので、内部でXMLを扱う必要がある。</p>
</div>
<div class="paragraph">
<p>XMLライブラリーは切り替え可能にしていて、今はRuby標準添付の<a href="https://docs.ruby-lang.org/ja/2.6.0/library/rexml.html">REXML</a>の他に<a href="https://nokogiri.org/">Nokogiri</a>をサポートしている。これに加えて<a href="https://gitlab.com/yorickpeterse/oga">Oga</a>もサポートしたいなあと前々から思っていた。</p>
</div>
<div class="paragraph">
<p>最近、全然プログラミングを楽しむ時間が取れていなくてあまりにフラストレーションが溜まってしまったので、他にやることもあったのだけど、Ogaに取り組むことにしてしまった。具体的には、Ogaの方の<a href="https://gitlab.com/yorickpeterse/oga/issues/176">Improve XPath namespace support</a>というイシューを解決しようというものだ。これができれば、EPUB Parserの方にOgaサポートを追加するのは簡単。</p>
</div>
<div class="paragraph">
<p>問題の特定や変更箇所は既にイシューページで議論されているので、後残っているのはOgaの構造を理解すること、それから、実際に手を動かしてパッチを書くことだ。ソースコードを眺めたり動かしてみたりして<a href="https://gitlab.com/yorickpeterse/oga/issues/176#note_45444258">Yorick Peterseさんの提案するAPI</a>を定義することはできたので、後はAPI経由で渡された名前空間エイリアスを使うところを実装すればいい、というところまで進んだ(未コミット)。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_fiddleでlibzip">Fiddleでlibzip</h2>
<div class="sectionbody">
<div class="paragraph">
<p>EPUBというのは「XHTMLとCSSと画像とメタデータ用XMLファイルをZIPアーカイブした物」なので、EPUB ParserではZIPアーカイブを扱う必要がある。これも切り替え可能にしていて、ピュアRubyの<a href="https://github.com/javanthropus/archive-zip">Archive::Zip</a>とCの<a href="https://libzip.org/">libzip</a>のバインディングである<a href="https://bitbucket.org/winebarrel/zip-ruby/wiki/Home">Zip/Ruby</a>をサポートしている。当然Zip/Rubyの方が速い。のだけど、長いこと開発が止まっているので、何とかしたいなあと思っていた。具体的には、自分でメンテナンスを引き継ぐか、別のlibzipバインディングを作るか、(Rustに興味あるので)RustのZIPクレートをRubyから使えるようにしてみるか(<a href="https://github.com/danielpclark/rutie">Rutie</a>などで)。</p>
</div>
<div class="paragraph">
<p>それとは別にRubyでFFIする時に<a href="https://github.com/ffi/ffi">Ruby FFI</a>ばっかりが使われて<a href="https://docs.ruby-lang.org/ja/2.6.0/library/fiddle.html">Fiddle</a>が見向きもされないのが何だかなあと思っていたので、二番目の「別のlibzipバインディングを作る」をFiddleを使ってやってみようと思った。</p>
</div>
<div class="paragraph">
<p>とは言え、Cプログラミングが全くの初心者なので、ドキュメント読んだり何とかlibzipのAPIリファレンス見たりしてやってみてるのだけどうまくできてるのか自信がない。特にメモリー管理が適切なのか全然分からん。途中経過がこんな感じ。どうなんでしょう?</p>
</div>
<div class="paragraph">
<p><a href="https://gitlab.com/snippets/1915940" class="bare">https://gitlab.com/snippets/1915940</a></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="nb">require</span> <span class="s2">"fiddle/import"</span>
<span class="k">module</span> <span class="nn">Libzip</span>
<span class="no">CREATE</span> <span class="o">=</span> <span class="mi">1</span>
<span class="no">EXCL</span> <span class="o">=</span> <span class="mi">2</span>
<span class="no">CHECKCONS</span> <span class="o">=</span> <span class="mi">4</span>
<span class="no">TRUNCATE</span> <span class="o">=</span> <span class="mi">8</span>
<span class="no">RDONLY</span> <span class="o">=</span> <span class="mi">16</span>
<span class="k">module</span> <span class="nn">FL</span>
<span class="no">NOCASE</span> <span class="o">=</span> <span class="mi">1</span>
<span class="no">NODIR</span> <span class="o">=</span> <span class="mi">2</span>
<span class="no">COMPRESSED</span> <span class="o">=</span> <span class="mi">4</span>
<span class="no">UNCHANGED</span> <span class="o">=</span> <span class="mi">8</span>
<span class="no">RECOMPRESS</span> <span class="o">=</span> <span class="mi">16</span>
<span class="no">ENCRYPTED</span> <span class="o">=</span> <span class="mi">32</span>
<span class="no">ENC_GUESS</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">ENC_RAW</span> <span class="o">=</span> <span class="mi">64</span>
<span class="no">ENC_STRICT</span> <span class="o">=</span> <span class="mi">128</span>
<span class="no">LOCAL</span> <span class="o">=</span> <span class="mi">256</span>
<span class="no">CENTRAL</span> <span class="o">=</span> <span class="mi">512</span>
<span class="no">ENC_UTF_8</span> <span class="o">=</span> <span class="mi">2048</span>
<span class="no">ENC_CP437</span> <span class="o">=</span> <span class="mi">4096</span>
<span class="no">OVERWRITE</span> <span class="o">=</span> <span class="mi">8192</span>
<span class="k">end</span>
<span class="kp">extend</span> <span class="no">Fiddle</span><span class="o">::</span><span class="no">Importer</span>
<span class="n">dlload</span> <span class="s2">"libzip.so"</span><span class="p">,</span> <span class="s2">"libzip.so.4"</span>
<span class="n">typealias</span> <span class="s2">"zip_flags_t"</span><span class="p">,</span> <span class="s2">"uint64_t"</span>
<span class="n">typealias</span> <span class="s2">"zip_uint16_t"</span><span class="p">,</span> <span class="s2">"uint64_t"</span>
<span class="n">typealias</span> <span class="s2">"zip_uint32_t"</span><span class="p">,</span> <span class="s2">"uint64_t"</span>
<span class="n">typealias</span> <span class="s2">"zip_uint64_t"</span><span class="p">,</span> <span class="s2">"uint64_t"</span>
<span class="n">typealias</span> <span class="s2">"zip_int64_t"</span><span class="p">,</span> <span class="s2">"zip_uint64_t"</span>
<span class="n">extern</span> <span class="s2">"zip_t * zip_open(const char *path, int flags, int *errorp)"</span>
<span class="n">extern</span> <span class="s2">"int zip_close(zip_t *archive)"</span>
<span class="n">extern</span> <span class="s2">"zip_int64_t zip_get_num_entries(zip_t *archive, zip_flags_t flags)"</span>
<span class="n">extern</span> <span class="s2">"zip_file_t * zip_fopen_index(zip_t *archive, zip_uint64_t index, zip_flags_t flags)"</span>
<span class="n">extern</span> <span class="s2">"int zip_fclose(zip_file_t *file)"</span>
<span class="n">extern</span> <span class="s2">"void zip_stat_init(zip_stat_t *sb)"</span>
<span class="n">extern</span> <span class="s2">"int zip_stat_index(zip_t *archive, zip_uint64_t index, zip_flags_t flags, zip_stat_t *sb)"</span>
<span class="no">Stat</span> <span class="o">=</span> <span class="n">struct</span><span class="p">([</span>
<span class="s2">"zip_uint64_t valid"</span><span class="p">,</span>
<span class="s2">"const char *name"</span><span class="p">,</span>
<span class="s2">"zip_uint64_t index"</span><span class="p">,</span>
<span class="s2">"zip_uint64_t size"</span><span class="p">,</span>
<span class="s2">"zip_uint64_t comp_size"</span><span class="p">,</span>
<span class="c1"># "time_t mtime",</span>
<span class="s2">"zip_uint32_t crc"</span><span class="p">,</span>
<span class="s2">"zip_uint16_t comp_method"</span><span class="p">,</span>
<span class="s2">"zip_uint16_t encryption_method"</span><span class="p">,</span>
<span class="s2">"zip_uint32_t flags"</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">class</span> <span class="nc">Stat</span>
<span class="no">NAME</span> <span class="o">=</span> <span class="mh">0x0001</span>
<span class="no">INDEX</span> <span class="o">=</span> <span class="mh">0x0002</span>
<span class="no">SIZE</span> <span class="o">=</span> <span class="mh">0x0004</span>
<span class="no">COMP_SIZE</span> <span class="o">=</span> <span class="mh">0x0008</span>
<span class="no">MTIME</span> <span class="o">=</span> <span class="mh">0x0010</span>
<span class="no">CRC</span> <span class="o">=</span> <span class="mh">0x0020</span>
<span class="no">COMP_METHOD</span> <span class="o">=</span> <span class="mh">0x0040</span>
<span class="no">ENCRYPTION_METHOD</span> <span class="o">=</span> <span class="mh">0x0080</span>
<span class="no">FLAGS</span> <span class="o">=</span> <span class="mh">0x0100</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">errorp</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">archive</span> <span class="o">=</span> <span class="no">Libzip</span><span class="p">.</span><span class="nf">zip_open</span><span class="p">(</span><span class="s2">"book.zip"</span><span class="p">,</span> <span class="no">Libzip</span><span class="o">::</span><span class="no">RDONLY</span><span class="p">,</span> <span class="n">errorp</span><span class="p">)</span>
<span class="n">pp</span> <span class="n">archive</span>
<span class="n">pp</span> <span class="n">num_entries</span> <span class="o">=</span> <span class="no">Libzip</span><span class="p">.</span><span class="nf">zip_get_num_entries</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="no">Libzip</span><span class="o">::</span><span class="no">FL</span><span class="o">::</span><span class="no">UNCHANGED</span><span class="p">)</span>
<span class="mi">0</span><span class="p">.</span><span class="nf">upto</span> <span class="n">num_entries</span> <span class="o">-</span> <span class="mi">1</span> <span class="k">do</span> <span class="o">|</span><span class="n">index</span><span class="o">|</span>
<span class="n">file</span> <span class="o">=</span> <span class="no">Libzip</span><span class="p">.</span><span class="nf">zip_fopen_index</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">stat</span> <span class="o">=</span> <span class="no">Libzip</span><span class="o">::</span><span class="no">Stat</span><span class="p">.</span><span class="nf">malloc</span>
<span class="no">Libzip</span><span class="p">.</span><span class="nf">zip_stat_init</span> <span class="n">stat</span>
<span class="nb">p</span> <span class="no">Libzip</span><span class="p">.</span><span class="nf">zip_stat_index</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="no">Libzip</span><span class="o">::</span><span class="no">Stat</span><span class="o">::</span><span class="no">NAME</span><span class="o">|</span><span class="no">Libzip</span><span class="o">::</span><span class="no">Stat</span><span class="o">::</span><span class="no">SIZE</span><span class="p">,</span> <span class="n">stat</span><span class="p">)</span>
<span class="nb">p</span> <span class="p">[</span><span class="n">index</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">stat</span><span class="p">.</span><span class="nf">size</span><span class="p">,</span> <span class="n">stat</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">to_s</span><span class="p">]</span>
<span class="nb">p</span> <span class="no">Libzip</span><span class="p">.</span><span class="nf">zip_fclose</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">pp</span> <span class="no">Libzip</span><span class="p">.</span><span class="nf">zip_close</span><span class="p">(</span><span class="n">archive</span><span class="p">)</span>
<span class="n">pp</span> <span class="n">archive</span>
<span class="c1"># pp Libzip.zip_close(archive) # => SEGV</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>FiddleはRuby標準添付のFFIライブラリーなので、追加gemをインストールする必要がない。おまけにWindowsでも動く。ので基本はこちらがいいんではないかなあ。内部ではFiddleを使いつつ、APIだけRuby FFIと同じにするための<a href="https://github.com/unak/fiddley">Fiddley</a>というgemもあるので、興味ある人はこれを使って移行を始めてもいいだろう。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_public_outbox">Public Outbox</h2>
<div class="sectionbody">
<div class="paragraph">
<p>自分のトゥートとかブログポストとかのActivityPubのアクティビティを一か所に集めて、「他人がここを見れば自分のアクティビティが分かる」というようにできたらどうだろう、と考えたりしてた。ActivityPubのoutboxのうちパブリックな物のリスト、とも考えられるので、Public Outboxという名前はどうか。というようなことを考えたりしていた。何で作るのがいいんだろう。Rails?</p>
</div>
<div class="paragraph">
<p>因みに名前は<a href="https://public-inbox.org/README.html">public-inbox</a>のもじり。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_集中力の衰え">集中力の衰え</h2>
<div class="sectionbody">
<div class="paragraph">
<p>以前だったら、OgaのXPath名前空間拡充は、ここで手を止めずにもっと進めていたと思うのだけど、今はそれができなくなってしまっている。歳なのかなんなのか、集中力が衰えてしまっているなあと感じる。まずいまずい。</p>
</div>
</div>
</div>
https://diary.kitaitimakoto.net/2019/11/23.html
雑多にプログラミング、集中力の衰え
2019-11-23T00:00:00Z
2019-11-23T00:00:00Z
<div class="paragraph">
<p>ここの「+」の所。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://gyazo.com/ce78a14fa17a3743411af09f8bf3b57c.png" alt="ce78a14fa17a3743411af09f8bf3b57c">
</div>
</div>
<div class="paragraph">
<p>これが何故かグレーアウトされたままでブロックが追加できなかった。<br>
「Gutenberg cannot add block」とかでググったら出てくる解決法の、「プロフィール画面で『 ビジュアルリッチエディターを使用しない』のチェックを外す」をしてもだめ。</p>
</div>
<div class="paragraph">
<p>なんでだろう思いつつ、「<a href="https://wordpress.org/plugins/classic-editor/">Classic Editor</a>」プラグインを入れてみたら、TinyMCEになるわけだけど、なんかHTMLしか入力を受け付けない、WYSIWYGモードにならない。ここで「WordPress wysiwyg not shown」とかでググったら、「WordPressはUAを見てリッチエディターの有効・無効を判定しているので、CloudFrontを使っているとリッチエディターが使えない」ということが分かる(例:<a href="https://dtbaker.net/blog/rich-textvisualwysiwyg-editor-does-not-work-in-wordpress-behind-cloudfront/">Rich Text/Visual/WYSIWYG Editor does not work in WordPress behind cloudfront – dtbaker.net</a>)。<br>
リンク先では <code>functions.php</code> を編集しているけど、CloudFrontでUser-Agentヘッダーをフォワードするように設定して解決。</p>
</div>
<div class="paragraph">
<p>Gutenberg関連だという思い込みがあって中々解決に至らなかった。CloudFrontは盲点だったなあ。<br>
もうソースコード読むしかないか……という気持ちだったので、そうなってたらまあ、発見できたかも知れないが相当時間か掛かりう。「Classic Editor」を入れる、というのを試して本当によかった。</p>
</div>
https://diary.kitaitimakoto.net/2019/10/28.html
WordPressでGutenbergのブロック追加ができない
2019-10-28T00:00:00Z
2019-10-28T00:00:00Z
<div class="paragraph">
<p>パソコンの目の前でポッドキャストを聴けない。</p>
</div>
<div class="paragraph">
<p>ポッドキャストは「ながら聴き」ができていい、という面がある。寝しなに目をつむったまま寝入りながら聞けるとか、通勤中に聞くとか、散歩しながら聞くとか。</p>
</div>
<div class="paragraph">
<p>でも、パソコン開いてぼーっとしながら聞いてると、いつの間にか別のことを考えていて、聴いてない。慌てて巻き戻す。ということを繰り返していた。なんか、いつの間にか、むしろポッドキャストの内容を切っ掛けにググったりしているのだけど、目の前に記事に集中してしまっている。僕は視覚優位なのかも知れない。</p>
</div>
https://diary.kitaitimakoto.net/2019/10/22.html
パソコンの目の前でポッドキャストを聴けない
2019-10-22T00:00:00Z
2019-10-22T00:00:00Z
<div class="paragraph">
<p>今週初めに<a href="https://taiko-ch.net/">太鼓の達人</a>をやり始めた。</p>
</div>
<div class="paragraph">
<p>最初は米津玄師の「Lemon」がクリアできるかできないかぐらいのレベルで、今日UNISON SQUARE GARDENの「シュガーソングとビターステップ」の「むずかしい」をクリアできるようになった<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>。</p>
</div>
<div class="paragraph">
<p>シュガーソングとビターステップとは不思議な縁があって、いつも転換を齎してくれる。<a href="https://p.eagate.573.jp/game/popn/peace/p/index.html">ポップンミュージック</a>では難易度HYPERからEXTRAに移る最初の曲として丁度いい難易度だったし、この曲を知らなかったけどYouTubeで検索してUruを知ることができたし、そして今また「ドン」「カ」が混じる太鼓の達人曲の練習相手となってくれている。特別好きな曲というわけではないのだが、縁がある。</p>
</div>
<div id="footnotes">
<hr>
<div class="footnote" id="_footnotedef_1">
<a href="#_footnoteref_1">1</a>. 例外的に米津玄師の「LOSER」だけはすぐにクリアできるようになった。やっぱり好きな曲は違う。
</div>
</div>
https://diary.kitaitimakoto.net/2019/10/06.html
シュガーソングとビターステップ
2019-10-06T00:00:00Z
2019-10-06T00:00:00Z
<div class="paragraph">
<p>連休で<a href="https://aichitriennale.jp/">あいちトリエンナーレ</a>に行こうと思っていたのだけどやらないといけないことを思い出してしまったので、諦めた。腹いせに、作業をしつつ、本を読んでいる。最近は一つの本をまとめて読むのでなく、細かく切り替えながら読んでいる。</p>
</div>
<div class="sect1">
<h2 id="_未明の闘争">未明の闘争</h2>
<div class="sectionbody">
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E6%9C%AA%E6%98%8E%E3%81%AE%E9%97%98%E4%BA%89%EF%BC%88%E4%B8%8A%EF%BC%89-%E8%AC%9B%E8%AB%87%E7%A4%BE%E6%96%87%E5%BA%AB-%E4%BF%9D%E5%9D%82%E5%92%8C%E5%BF%97-ebook/dp/B01CE4DVKU?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B01CE4DVKU" target="_blank"><img src="https://images-fe.ssl-images-amazon.com/images/I/41PF1ERRlhL._SL160_.jpg" width="106" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E6%9C%AA%E6%98%8E%E3%81%AE%E9%97%98%E4%BA%89%EF%BC%88%E4%B8%8A%EF%BC%89-%E8%AC%9B%E8%AB%87%E7%A4%BE%E6%96%87%E5%BA%AB-%E4%BF%9D%E5%9D%82%E5%92%8C%E5%BF%97-ebook/dp/B01CE4DVKU?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B01CE4DVKU" target="_blank">未明の闘争(上) (講談社文庫)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="https://booklog.jp/author/%E4%BF%9D%E5%9D%82%E5%92%8C%E5%BF%97" target="_blank">保坂和志</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">講談社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2016-02-13</div></div><div class="booklog_html_link_amazon"><a href="https://booklog.jp/item/1/B01CE4DVKU" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<div class="paragraph">
<p>今8%で、まだまだ。書き方に慣れてきて、癖になり始めてる。</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>客の歌っているラルクアンシエルやグレイはうまかったがカラオケをうまく歌うくらいバカなことはない。</p>
</div>
</blockquote>
<div class="attribution">
— 未明の闘争(上)
</div>
</div>
<div class="paragraph">
<p>というのがあって、ああいいなあと思う。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_小説の誕生_小説の自由">小説の誕生 小説の自由</h2>
<div class="sectionbody">
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E5%B0%8F%E8%AA%AC%E3%81%AE%E8%AA%95%E7%94%9F-%E5%B0%8F%E8%AA%AC%E3%81%AE%E8%87%AA%E7%94%B1-%E4%B8%AD%E5%85%AC%E6%96%87%E5%BA%AB-%E4%BF%9D%E5%9D%82%E5%92%8C%E5%BF%97-ebook/dp/B00AQY9A4M?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B00AQY9A4M" target="_blank"><img src="https://images-fe.ssl-images-amazon.com/images/I/41v31%2BXXK4L._SL160_.jpg" width="104" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E5%B0%8F%E8%AA%AC%E3%81%AE%E8%AA%95%E7%94%9F-%E5%B0%8F%E8%AA%AC%E3%81%AE%E8%87%AA%E7%94%B1-%E4%B8%AD%E5%85%AC%E6%96%87%E5%BA%AB-%E4%BF%9D%E5%9D%82%E5%92%8C%E5%BF%97-ebook/dp/B00AQY9A4M?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B00AQY9A4M" target="_blank">小説の誕生 小説の自由 (中公文庫)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="https://booklog.jp/author/%E4%BF%9D%E5%9D%82%E5%92%8C%E5%BF%97" target="_blank">保坂和志</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">中央公論新社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2011-08-25</div></div><div class="booklog_html_link_amazon"><a href="https://booklog.jp/item/1/B00AQY9A4M" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<div class="paragraph">
<p>80%ぐらいで、読み終わるのが勿体無い。神の話を過ぎたあたり。ほんとどのページも面白いなあ。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_コミュニティマーケティング">コミュニティマーケティング</h2>
<div class="sectionbody">
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E3%83%93%E3%82%B8%E3%83%8D%E3%82%B9%E3%82%82%E4%BA%BA%E7%94%9F%E3%82%82%E3%82%B0%E3%83%AD%E3%83%BC%E3%82%B9%E3%81%95%E3%81%9B%E3%82%8B-%E3%82%B3%E3%83%9F%E3%83%A5%E3%83%8B%E3%83%86%E3%82%A3%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0-%E5%B0%8F%E5%B3%B6%E8%8B%B1%E6%8F%AE-ebook/dp/B07RWBZJBB?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B07RWBZJBB" target="_blank"><img src="https://images-fe.ssl-images-amazon.com/images/I/51NiAzgz4iL._SL160_.jpg" width="104" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E3%83%93%E3%82%B8%E3%83%8D%E3%82%B9%E3%82%82%E4%BA%BA%E7%94%9F%E3%82%82%E3%82%B0%E3%83%AD%E3%83%BC%E3%82%B9%E3%81%95%E3%81%9B%E3%82%8B-%E3%82%B3%E3%83%9F%E3%83%A5%E3%83%8B%E3%83%86%E3%82%A3%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0-%E5%B0%8F%E5%B3%B6%E8%8B%B1%E6%8F%AE-ebook/dp/B07RWBZJBB?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B07RWBZJBB" target="_blank">ビジネスも人生もグロースさせる コミュニティマーケティング</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="https://booklog.jp/author/%E5%B0%8F%E5%B3%B6%E8%8B%B1%E6%8F%AE" target="_blank">小島英揮</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">日本実業出版社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2019-03-20</div></div><div class="booklog_html_link_amazon"><a href="https://booklog.jp/item/1/B07RWBZJBB" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<div class="paragraph">
<p>仕事のために細々と読んでいる。書き方が明快。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_python機械学習プログラミング">Python機械学習プログラミング</h2>
<div class="sectionbody">
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/Python-%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E9%81%94%E4%BA%BA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%82%B9%E3%83%88%E3%81%AB%E3%82%88%E3%82%8B%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%B7%B5-impress-gear/dp/4295003379?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=4295003379" target="_blank"><img src="https://images-fe.ssl-images-amazon.com/images/I/61qCGR2QqGL._SL160_.jpg" width="117" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/Python-%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E9%81%94%E4%BA%BA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%82%B9%E3%83%88%E3%81%AB%E3%82%88%E3%82%8B%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%B7%B5-impress-gear/dp/4295003379?SubscriptionId=0AVSM5SVKRWTFMG7ZR82&tag=booklog.jp-22&linkCode=xm2&camp=2025&creative=165953&creativeASIN=4295003379" target="_blank">[第2版]Python 機械学習プログラミング 達人データサイエンティストによる理論と実践 (impress top gear)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="https://booklog.jp/author/Sebastian+Raschka" target="_blank">Sebastian Raschka</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">インプレス</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2018-03-16</div></div><div class="booklog_html_link_amazon"><a href="https://booklog.jp/item/1/4295003379" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<div class="paragraph">
<p>「コンピューターにデータを与えてパターンを学習させることができる」というぐらいのふわっとした理解だったところにどんどん形を与えてくれるので興奮して読んでいる。</p>
</div>
<div class="paragraph">
<p>(Kindleとかの電子書籍ストアじゃなく)PDF版を購入できるのがありがたい。</p>
</div>
</div>
</div>
https://diary.kitaitimakoto.net/2019/08/11.html
未明の闘争、小説の誕生 小説の自由、コミュニティマーケティング、Python機械学習プログラミング
2019-08-11T00:00:00Z
2019-08-11T00:00:00Z
<div class="paragraph">
<p>この前(<a href="../../2019/06/23.html">まんが読むのに適した環境</a>)の続き的な話。</p>
</div>
<div class="paragraph">
<p>「まんがって、みんな表示したい要件一緒なのに<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>、どうもウェブでの標準的な表示ツール無いよなあ。自分で作ってみたらいいのかも知れないけど、それって単に『また一つ独自のやり方が増えた』ってことにしかならないよなあ。」とか日々思っていたのですが、<a href="https://iipimage.sourceforge.io/documentation/server/">IIIF</a>なら、画像の配信方法は世界共通で規定されているし、表示ツールも既に複数あるから、いいの見付けてきてまんが向けに手を入れれば読みやすくできるかも、と思い付いたのでした。<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup></p>
</div>
<div class="paragraph">
<p>ので、やってみました。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://i.gyazo.com/f31de6072f545c663bc68333c3a2b99e.jpg" alt="IIIF用のMiradorビューワーでまんがを表示したところ">
</div>
<div class="title">Figure 1. 『ブラックジャックによろしく』(© 佐藤秀峰)</div>
</div>
<div class="sect1">
<h2 id="_まとめ">まとめ</h2>
<div class="sectionbody">
<div class="ulist">
<ul>
<li>
<p>IIIFでまんがを閲覧できるようにしてみた</p>
</li>
<li>
<p>wax_iiifというツールで、サーバーメンテなしでまんが配信・閲覧ができた</p>
</li>
<li>
<p>まんがにメモ書き(アノテーション)を付けて、後からそれを検索とかできた</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_wax_iiif">wax_iiif</h2>
<div class="sectionbody">
<div class="paragraph">
<p>IIIFでは一つの大きな画像を、複数の大きさ(小ささ)にリサイズして用意したり、何枚かの正方形の画像に分割してブラウザー側で貼り合わせて大きく見せる、といった使い方を念頭に置いていて、それを実現するために、ユーザーがアクセスした時にその場で画像加工をして配信するサーバー、というのがいくつも作られているんですけど、そういうサーバーをあんまり運用したくないから簡単なやつがいいなあ……と思っているところで、<a href="https://github.com/cmoa/iiif_s3">IiifS3</a>というツールを見付けたのでした。ただ、今はメンテされてなくて、これをフォークした<a href="https://github.com/minicomp/wax_iiif">wax_iiif</a>というのの使い方を調べました。</p>
</div>
<div class="paragraph">
<p>wax_iiifはなんと、リサイズと画像分割をしたファイルを作成して、必要なメタデータ等のJSONファイルを生成してくれるツールです。アクセス時にその場で画像を生成するのではなくて、単にそのまま配信すればいい形でファイルをあらかじめ生成してくれます。出来上がったものをApacheとかで配信したり、S3とかに置いてウェブサイトにするだけで、もうIIIF対応は終わり、お好みのビューワーでみられるようになります。</p>
</div>
<div class="paragraph">
<p>実際生成して、ビューワーとして<a href="見ることができるようになります。">Mirador</a>を使うようにしてみたのがこちら。リンク先で実際にまんがを読むことができます。</p>
</div>
<div class="paragraph">
<p><a href="https://kitaitimakoto.gitlab.io/manga-annotation/mirador2.html" class="bare">https://kitaitimakoto.gitlab.io/manga-annotation/mirador2.html</a><br>
<a href="https://kitaitimakoto.gitlab.io/manga-annotation/mirador2.html"><span class="image"><img src="https://i.gyazo.com/e4e2f3325696fc8c0d58da8678697a62.jpg" alt="IIIFで『ブラックジャックによろしく』を配信・閲覧" width="300"></span></a></p>
</div>
<div class="paragraph">
<p>(表示しているまんがは、佐藤秀峰さんが二次利用フリーで公開している『ブラックジャックによろしく』第一巻です。ありがとうございます。まんがのダウンロードと利用に際しての注意事項等はこちら: <a href="http://mangaonweb.com/company/download.html" class="bare">http://mangaonweb.com/company/download.html</a> )</p>
</div>
<div class="paragraph">
<p>実際にどうやってwax_iiifを使ってサイトを作るのか、というのは、こちらのRakefileを見てみてください: <a href="https://gitlab.com/KitaitiMakoto/manga-annotation/blob/1777d5816de0133fbfbebc072a9ba47073fd8bee/Rakefile">Rakefile</a><br>
一部抜粋するとこんな感じ:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">file</span> <span class="s2">"build/001bj/collection/top.json"</span> <span class="o">=></span> <span class="p">[</span><span class="s2">"images/001bj/001bj-210-0.png"</span><span class="p">]</span> <span class="k">do</span> <span class="o">|</span><span class="n">task</span><span class="o">|</span>
<span class="n">builder</span> <span class="o">=</span> <span class="no">WaxIiif</span><span class="o">::</span><span class="no">Builder</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">base_url: </span><span class="no">ENV</span><span class="p">[</span><span class="s2">"BASE_URL"</span><span class="p">],</span>
<span class="ss">output_dir: </span><span class="s2">"build/001bj"</span><span class="p">,</span>
<span class="ss">verbose: </span><span class="kp">true</span>
<span class="p">)</span>
<span class="n">records</span> <span class="o">=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">glob</span><span class="p">(</span><span class="s2">"images/001bj/**"</span><span class="p">).</span><span class="nf">collect</span> <span class="p">{</span><span class="o">|</span><span class="n">path</span><span class="o">|</span>
<span class="no">WaxIiif</span><span class="o">::</span><span class="no">ImageRecord</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">id: </span><span class="no">File</span><span class="p">.</span><span class="nf">basename</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
<span class="ss">path: </span><span class="n">path</span><span class="p">,</span>
<span class="ss">is_primary: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">attribution: </span><span class="s2">"タイトル:ブラックジャックによろしく、著作者名:佐藤秀峰"</span><span class="p">,</span>
<span class="ss">license: </span><span class="s2">"http://mangaonweb.com/company/download.html#kiyaku"</span><span class="p">,</span>
<span class="ss">description: </span><span class="s2">"佐藤秀峰著『ブラックジャックによろしく』第1巻の1ページです。作品と二次利用に関する詳細は http://mangaonweb.com/company/download.html をご覧ください。"</span><span class="p">,</span>
<span class="ss">label: </span><span class="p">[</span>
<span class="s2">"タイトル: ブラックジャックによろしく"</span><span class="p">,</span>
<span class="s2">"著作者名: 佐藤秀峰"</span><span class="p">,</span>
<span class="s2">"詳細情報: http://mangaonweb.com/company/download.html"</span>
<span class="p">],</span>
<span class="ss">metadata: </span><span class="p">[</span>
<span class="p">{</span>
<span class="s2">"label"</span><span class="p">:</span> <span class="s2">"著者"</span><span class="p">,</span> <span class="ss">value: </span><span class="s2">"佐藤秀峰"</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="n">builder</span><span class="p">.</span><span class="nf">load</span> <span class="n">records</span>
<span class="n">builder</span><span class="p">.</span><span class="nf">process_data</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>画像のパスとメタデータを渡して <code>WaxIiif::Builder#process_data</code> を呼ぶだけですね。</p>
</div>
<div class="paragraph">
<p>出来上がった物は<a href="https://about.gitlab.com/product/pages/">GitLab Pages</a>にしているので、自分でサーバーをメンテすることなく済んでいます。便利。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_メモ書きアノテーション">メモ書き(アノテーション)</h2>
<div class="sectionbody">
<div class="paragraph">
<p>僕はまんが読む時、ページやコマにメモを取りながら読むので、そういうのができるといいですね。</p>
</div>
<div class="paragraph">
<p>IIIFは学術目的なので当然(?)メモ書き (アノテーションと呼ぶ)の仕様もある程度あって、ビューワー側が対応していたり(上で使ってるMiradorも)、メモ書き保存用のサーバーもあったりします。<sup class="footnote">[<a id="_footnoteref_3" class="footnote" href="#_footnotedef_3" title="View footnote.">3</a>]</sup></p>
</div>
<div class="paragraph">
<p>これはサーバーなしというわけにはいかなくて、取り敢えずは<a href="https://github.com/glenrobson/SimpleAnnotationServer">SimpleAnnotationServer</a>というのを手元のパソコンで試してみました。こんな感じでアノテーションを付けることができます。</p>
</div>
<div class="videoblock">
<div class="title">画像の範囲を選択してアノテーション(メモ書き)を付けられる</div>
<div class="content">
<video src="https://i.gyazo.com/a09c24e15b4a2e01e20808779b0993d2.mp4" poster="/images/カラーページに「カラーページ」というアノテーションをつけていくところ" controls>
Your browser does not support the video tag.
</video>
</div>
</div>
<div class="paragraph">
<p>後からその画像を見る時に、アノテーションも一緒に見ることができます。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://i.gyazo.com/8e9405bad1e6bab4c90f1cc748a42c48.png" alt="画像を見る際、その画像にに付けられたアノテーションも表示される">
</div>
</div>
<div class="paragraph">
<p>そして、アノテーションで検索もできます。</p>
</div>
<div class="videoblock">
<div class="title">アノテーションをキーワード検索し、そのアノテーションが付けられた画像へジャンプできる</div>
<div class="content">
<video src="https://i.gyazo.com/9f9d607109348e6bf46ad6d9d312d152.mp4" poster="/images/「カラー」というキーワードで検索し、結果をタップすることでそのページにジャンプできる" controls>
Your browser does not support the video tag.
</video>
</div>
</div>
<div class="paragraph">
<p>これは便利すぎる。</p>
</div>
<div class="paragraph">
<p>こういうことをする基本的なパーツが揃っているというのはありがたいなあ。ウェブでの画像配信が標準化されている恩恵を感じます。自分の好きなように組み合わせればいい。</p>
</div>
<div class="paragraph">
<p>この後は、UIを自分好みに調整したい気持ちはあります。タグ付けできたり、検索ももうちょっとみやすくしたいですね。そういった細々した展望やメモ書きはこちらにあるのでよかったら見てみてください:<br>
<a href="https://scrapbox.io/apehuci/%E3%81%BE%E3%82%93%E3%81%8C%E3%82%92IIIF%E3%81%A7%E8%A1%A8%E7%A4%BA">まんがをIIIFで表示 - アペフチ </a></p>
</div>
<div class="paragraph">
<p>この日記でもこの辺は書いていきたい。</p>
</div>
</div>
</div>
<div id="footnotes">
<hr>
<div class="footnote" id="_footnotedef_1">
<a href="#_footnoteref_1">1</a>. まんがアプリやツイッターや縦スクロールでちょっとずつ変わってきてますけどね。
</div>
<div class="footnote" id="_footnotedef_2">
<a href="#_footnoteref_2">2</a>. IIIFについては日を改めて書きたいとも思っていますが、<a href="http://digitalnagasaki.hatenablog.com/iiif">IIIFに関する日本語情報の私的なまとめ</a>という記事から辿って読んでみると分かると思います。というか僕もこの方の記事を色々と読みました。
</div>
<div class="footnote" id="_footnotedef_3">
<a href="#_footnoteref_3">3</a>. サーバーはちょっと少ないなという印象です。あと、保存と表示はできても検索ができないとかもある。
</div>
</div>
https://diary.kitaitimakoto.net/2019/07/15.html
まんがを、IIIFで、サーバーなしで表示してみる
2019-07-15T00:00:00Z
2019-07-15T00:00:00Z
<div class="paragraph">
<p>まんがの各ページが画像として手元にあるとしよう。これを閲覧するのにいい環境は何かなあとぼんやり考えている。</p>
</div>
<div class="paragraph">
<p>いやまあ、本当に閲覧するだけなら、タブレットでもmacOSのプレビューアプリでもなんでもいいんだけど。でも閲覧といったら、気に入ったページはブックマークしておきたいし、感じたことは書き留めておきたい。一冊の本の中でも気に入ったエピソードだけ何回も読みたいから選り分けておきたい。当然、一つの端末でやったことは他の端末にも反映されていてほしい。そういった時に統合的に色々できる環境っていうのはなんだろうって思って、そういうの、作ってみたいなあと思っている。</p>
</div>
<div class="paragraph">
<p>今は、まんが読んでて気に入ったページはスクショした後<a href="https://gyazo.com/">Gyazo</a>に上げてる。コメントしたい時は、その画像に<a href="https://scrapbox.io/">Scrapbox</a>からリンクして、思ったことを書いている。<span class="image"><img src="https://i.gyazo.com/46fd437e4cbe8a97b29bce6a5809e790.png" alt="46fd437e4cbe8a97b29bce6a5809e790"></span>でもはっきり言って、めちゃめちゃめんどい。</p>
</div>
<div class="paragraph">
<p>なんか、既存の画像ビューワーでみたらいいのかなあと思って、<a href="https://iiif.io/apps-demos/">IIIF実装</a>を試したりしてみてるけど、IIIFはやっぱり目的違うので、「物語に集中して次々ページをめくる」というところに微妙に不自由を感じる。コメントとかできるのはいいんだけどね。</p>
</div>
<div class="paragraph">
<p>というわけでIIIF実装をもとに拡張していくのがいいのかなあ、それとも自作するのがいいのかなあ、などと思ってます。いいのあったら教えてください。自作するのにもIIIF実装をもとにするより、<a href="https://firebase.google.com/">Firebase</a>とかで作った方が楽は楽だなあとか。</p>
</div>
<div class="sect1">
<h2 id="_追記">追記。</h2>
<div class="sectionbody">
<div class="paragraph">
<p>続きっぽい記事を書きました:<a href="../../2019/07/15.html">まんがを、IIIFで、サーバーなしで表示してみる</a></p>
</div>
</div>
</div>
https://diary.kitaitimakoto.net/2019/06/23.html
まんが読むのに適した環境
2019-06-23T00:00:00Z
2019-06-23T00:00:00Z
<div class="paragraph">
<p>『<a href="https://gihyo.jp/dp/ebook/2019/978-4-297-10560-0">実践Rust入門 [言語仕様から開発手法まで]</a>』を買いました。</p>
</div>
<div class="paragraph">
<p>まだ目次を見ながら気になるところだけ拾い読みしている段階だけど(何しろ『<a href="https://www.oreilly.co.jp/books/9784873118550/">プログラミングRust</a>』もまだ読み掛け……)、ふと、ソースコードがシンタックスハイライトされていないことに気が付きました。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://gyazo.com/f8ad37110382b986cacf4e96402505ce.png" alt="ハイライトされていないソースコード" width="540">
</div>
</div>
<div class="paragraph">
<p>ので、ハイライトできるようにしてみました。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://gyazo.com/efa6f4feb95975416206cf092223cbd2.png" alt="ハイライトされているソースコード" width="540">
</div>
</div>
<div class="paragraph">
<p><a href="https://gitlab.com/KitaitiMakoto/pirka">Pirka</a>というコマンドラインツールでこれができます。PirkaはRubyGemとして配布しているので、 <code>gem</code> コマンドでインストールします。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% gem install pirka</pre>
</div>
</div>
<div class="paragraph">
<p>Pirkaは、外部でGit管理している「EPUB内のソースコードのプログラミング言語の辞書」を使用しているので、その辞書の最新版を引っ張ってきます。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% pirka update</pre>
</div>
</div>
<div class="paragraph">
<p>次に本を用意します。Gihyo Digital PublishingからEPUBファイルをダウンロードします(Kindleで買っている場合はこれはできません! 先に言っておくべきだった)。ここでは「./実践Rust入門[言語仕様から開発手法まで]_00.epub」という場所に置いたとします(Pirkaは本をハイライト版で置き換えます。必要に応じてバックアップしておいてください)。</p>
</div>
<div class="paragraph">
<p><code>pirka</code> コマンドを実行します。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% pirka ./実践Rust入門[言語仕様から開発手法まで]_00.epub</pre>
</div>
</div>
<div class="paragraph">
<p>暫く待つとコマンドが正常終了するので、ファイルを開くことでシンタックスハイライト版の『実践Rust入門』が読めます。(ただ、iBooksはなんかハイライトされないので、別の環境で見てもらったらいいのかな、何でだろう……。要調査。)</p>
</div>
<div class="paragraph">
<p>Kindleで読みたい場合は、Kindle Formatに変換してください。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% gem install kindlegen
% kindlegen ./実践Rust入門[言語仕様から開発手法まで]_00.epub</pre>
</div>
</div>
https://diary.kitaitimakoto.net/2019/05/03.html
『実践Rust入門』をシンタックスハイライトして読む
2019-05-03T00:00:00Z
2019-05-03T00:00:00Z
<div class="paragraph">
<p>途中しばらく読まなくなってまた続きを読み始めたり、気に入った同じところを何度も読んだり、ちょっとした中断は再三やっていて、戻った時にどこまで読んだか分からなくなって記憶のあるところからまた読み直したらその先も実は読んだことある所だったけど構わず重複して読んだりということを繰り返して一年半ぐらい掛かった『<a href="http://www.kawade.co.jp/np/isbn/9784309414225/">カンバセイション・ピース</a>』を読み終えました。筋とか出来事とか感情とか別にない(重要じゃない)ので早く続きを読みたい、読まなくちゃっていう焦りもないし、一度離れてまた戻ってもなんの問題もないという安心感があったりでリラックスして読み進めないことができていた、それなのに本を離れている間もずっと読んでいる気がしていて、多少の緊張感もあって、「読むのを諦めよう」という気になったりしない本でした。世界観が変わった気がする。</p>
</div>
https://diary.kitaitimakoto.net/2019/05/01.html
カンバセイション・ピース
2019-05-01T00:00:00Z
2019-05-01T00:00:00Z
<div class="paragraph">
<p>
<div style="font-family: serif; padding: 1em; line-height: 2em; border-left: 5px solid #ee6e73;">
ケースから取り出して作ったばかりの眼鏡のフレームを耳に乗せるとファミレスのメニューブックのステーキの写真が目に飛び込んで来たのだが、一瞬後にまたぼやけた。それから改めてゆっくりと焦点が合い、周辺のグラスや皿なんかもよく見え始める。<br>
眼鏡をかける動作によって眼筋が混乱したのだろう。これまでの度の合ってない眼鏡にアジャストされていた眼筋たちは僕の眼鏡を掛けるという動作を引き金に条件反射でこれまで通りの収縮或いは弛緩をした。これはつまり、眼筋は水晶体で屈折して入ってくる光の焦点を自動的にうまく合わせたり、僕の脳からの命令でわざと焦点をずらしたりするという器官ではなくて、眼鏡を掛ける、という腕の動きを覚えさせられた、独自の記憶を持つという器官なのかも知れないと思った。
</div>
</p>
</div>
https://diary.kitaitimakoto.net/2019/04/27.html
眼鏡を掛けると
2019-04-27T00:00:00Z
2019-04-27T00:00:00Z
<div class="paragraph">
<p><a href="https://bookwor.ms/@skoji">@skoji</a>さんが「<a href="https://gihyo.jp/magazine/wdpress/archive/2019/vol110">WEB+DB PRESS vol.110</a>」を買った、と言ってスクショを張っていたのが笹田耕一さんの「Rubyのウラガワ」という連載の第一回「オブジェクトはどうやって表現するのか?」で、面白そうですねという話をしていた( <a href="https://bookwor.ms/@skoji/101986558374837620" class="bare">https://bookwor.ms/@skoji/101986558374837620</a> )。そうしたら<a href="https://bookwor.ms/@takahashim">@takahashim</a>さんも記事を書いていたらしく、その紹介をされたので、買って、まずはそちらを読んでみた。</p>
</div>
<div class="paragraph">
<p>ところ、なんとSPARQLの話で(「SPARQLで知識データベースを自在に検索! オープンデータのためのWikidata入門」)、興奮して職場で声を出して驚いてしまった。SPARQLは、ちゃんと学びたいなあと思いつつ何度も挫折したクエリー言語で、オープンデータの文脈でこの記事では紹介されている。ありがたい。</p>
</div>
https://diary.kitaitimakoto.net/2019/04/26.html
WEB+DB PRESS Vol.110
2019-04-26T00:00:00Z
2019-04-26T00:00:00Z
<div class="paragraph">
<p>
<div style="font-family: serif; padding: 1em; line-height: 2em; border-left: 5px solid #ee6e73;">
原宿駅表参道出口を出て右に進み、すぐの交差点から神宮前交差点側を向くと一ブロック離れたその交差点はまるで壁で、視界はその向こうまで通るけれど交差点のところで意識はくっきりと区切られていた。左手前には並木が一本植えられ、道を挟んだ右側にも対応するようにまた一本並べられているそれらの十歩前方にやはり一本ずつ。それと同じ並木が神宮前交差点の方からも向かってきていて、やや曲がった道なりに遠近法で少しずつ左右の幅を広げ向かってきていた。両方から伸びた並木の丁度ぶつかるところに目線は定まって、その時意識に入り込んでくるのは自動車よりも歩行者、そこの横断歩道で信号待ちしている人達だった。<br>
道の半ばまで進んだ僕はその待ち行列の後ろに並ぶ、のではなく、対岸に向かって一番右にいる人のさらに右に立つ。視界の端の焦点の合わない部分を並木から垂れ下がった枝葉が占めてやや薄暗い中誘導される視線は横断歩道に沿っていて、その向こうに道幅一つ分左にずれて奥に入る小道がある。僕はそこを通って通勤する。その入り口を見詰めながら軽く仰ぐとそこは対岸の木の左側になっていて空が青い。青い空を見た時のすっと抜ける感じで僕は今日の自分の調子がいいのを知る。<br>
信号が青に変わった。
</div>
</p>
</div>
https://diary.kitaitimakoto.net/2019/04/01.html
原宿駅から神宮前交差点を望む
2019-04-01T00:00:00Z
2019-04-01T00:00:00Z
<div class="paragraph">
<p><a href="https://school.corkagency.com/">コルクラボマンガ専科</a>の課題の一つに「日常をスケッチして <a href="https://twitter.com/hashtag/%E3%82%B3%E3%83%AB%E3%82%AF%E3%83%A9%E3%83%9C%E3%83%9E%E3%83%B3%E3%82%AC%E5%B0%82%E7%A7%91?f=tweets&vertical=default&src=hash">#スケッチブックス</a> のハッシュタグで公開する」という物があります。僕は(まんが家志望の受講生でなく)聴講生なので投稿の義務はないのですが、楽しそうなので投稿してみます。まんがではなく文字ですがご容赦を。</p>
</div>
<div class="paragraph">
<p>
<div style="font-family: serif; padding: 1em; line-height: 2em; border-left: 5px solid #ee6e73;">
僕の同僚は舌打ちをする。彼女は上手に舌打ちをする。<br>
舌打ちというのは難しい物だ。僕は大体音が大きい。客観的に見て、人より音が大きい、ということはないと思うのだけどする時はいつも自分が思っていたより大きな音がしてしまう。会社でリポビタンDを飲んで空き瓶を捨てようとごみ入れを手前に引き出し(職場では収納棚の一エリアを空き缶入れに、一エリアを空き瓶入れに割り当てている)、中をろくに見ずに投げ入れたところ金属とガラスのぶつかる、人を驚かせがちな音をさせてしまったことがある。予想していたのと違う結果にしてしまった、自分で自分の振る舞いをコントロールできていない、こういうことに僕は弱い。思わず舌を打つ。これまた予想外に大きな音がする。コントロールできていない。誰が聞いているわけでもないのに、恥ずかしくなって、誤魔化すように、いや、誤魔化すために小さい舌打ちを何度も続けてちょっ、ちょっ、ちょっとやることで、飽くまで自分で意識しながらやっていることですよ、というアピールをしたりなんかする。<br>
<span style="margin-left: -1em;">「</span>北市さんも舌打ちってするんですねー」と、通り掛かった同僚が驚く。<br>
聞いてる奴、もとい、聞いている人がいた。しかもちょっ、ちょっ、ちょっ、の意図は通じていなくて、思わず舌打ちした事実が伝わってしまっている。もうだめだ。<br>
<span style="margin-left: -1em;">「</span>え、しますよ、割と。独り言もよく言うし」<br>
しどろもどろに言い訳になっていない言い訳をしながら(そもそも言い訳は必要ない)自分の瓶をごみ入れの外に出した後、中の箱にごみ袋を被せてまた瓶を入れる。底にまで手を伸ばして置く。音をさせない。その間に同僚は離れて行き、僕は目を合わせずに済む。<br>
僕の同僚は上手に舌打ちをする。<br>
ある日、日報を書いた時に(日報は社内で公開されている)彼女がコメントをくれて、そこからひとしきり会話をしたことがある。それは東京と地方の違いについてで、彼女の地元が茨城であることを僕はそこで初めて知った。そのつもりだった。でも彼女は以前にも言ったという確信があるらしくて、それを憶えていないことが不満な様子を、親指を立てた握りこぶしを下に向けた絵文字で示した。Go to hell.<br>
翌日彼女を含めた何人かとの間で、動物園の話になった。彼女は大のパンダ好きで、むしろ彼女自身がパンダだ(と本人は主張している)。和歌山の動物園で生まれたパンダに、名前が付けられたというニュースをアレクサが教えてくれたので、そんな彼女に報告に行ったのだった。<br>
<span style="margin-left: -1em;">「</span>そう言えば札幌の動物園にホッキョクグマ館が出来ていて、ホッキョクグマとアザラシが展示されていましたよ」と、札幌に実家を持つ僕が言う。<br>
<span style="margin-left: -1em;">「</span>いいね」<br>
<span style="margin-left: -1em;">「</span>正月に見て来ました」<br>
<span style="margin-left: -1em;">「</span>うちの地元の動物園はね……」<br>
<span style="margin-left: -1em;">「</span>そう言えば茨城出身でしたっけ?」<br>
彼女は青の濃いアイラインの目で僕を睨み、頬を膨らませて見せ、舌打ちをする。その迫力に僕は条件反射で縮こまる。<br>
一拍を置いて彼女が眉根の皺を解き目尻を下げた。ほっとする。<br>
自分の振る舞いをコントールできている。僕もコントロールされて、緊張と弛緩を楽しめている。<br>
彼女は上手に舌打ちをする。
</div>
</p>
</div>
https://diary.kitaitimakoto.net/2019/03/17.html
僕の同僚は舌打ちをする。
2019-03-17T00:00:00Z
2019-03-17T00:00:00Z
<div class="sect2">
<h3 id="_kinoppyが結構いい">Kinoppyが結構いい</h3>
<div class="paragraph">
<p><a href="https://k-kinoppy.jp/">Kinoppy</a>っていう電子書籍ストアアプリが結構いい、ということに気が付いた。これは紀伊国屋書店が出している、ストアが統合された電子書籍アプリで、買うことと読むことは勿論できるのだけど、端末にダウンロードしたEPUBファイルも読める。ストアとしては前から利用していたけど、今日いいなって思ったのはEPUBリーダーとしてのKinoppy。</p>
</div>
<div class="paragraph">
<p>AndroidでEPUBを読むのにずっとHimawari Reader Proっていうアプリを使っていたんだけど、新しく買ったタブレットでEPUBファイルの電子書籍をダウンロードして読もうと思ったらなかった。Androidはアカウント連携すれば自動的にアプリも入れてくれるのでそれに任せていたのだけど、漏れたのかなと思って探してもストアにもない。取り下げたのかなあ。(サブスクリプションでなく)買い切りのアプリだし、Androidのバージョンアップに追従していくの辛いですよね。。。</p>
</div>
<div class="paragraph">
<p>で、幾つかEPUBを読めるアプリを試していたのだけど、意外なことに(?)Kinoppyがよかった。</p>
</div>
<div class="ulist">
<ul>
<li>
<p>日本語文字もまともにレンダリングしてくれる</p>
</li>
<li>
<p>本文を選択してクリップボードにコピーできる</p>
</li>
<li>
<p>ページめくりとスクロールが選べる(スクロールで読みたいんだけど選べないアプリも結構あるのです)</p>
</li>
<li>
<p>縦書きの本も強制的に横書きにできる(縦書き読み慣れてなくて、内容に集中できるようになるまで時間が掛かる・・・)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>かなりいい。しばらくKinoppyで行きます。(スマホの方はHimawari入ってるのでこれで行きます。)</p>
</div>
</div>
<div class="sect2">
<h3 id="_tcfm_川合史朗さん回">#tcfm 川合史朗さん回</h3>
<div class="paragraph">
<p>例によって寝しなと起き抜けにポッドキャストを聞いている。昨夜〜今朝は<a href="https://turingcomplete.fm/">Turing Complete FM</a>の川合史朗さん回。</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><a href="https://turingcomplete.fm/14">14. 少年時代にPCを(本当の意味で)自作した話</a></dt>
<dd>
<p>川合さんは<a href="http://practical-scheme.net/gauche/">Gauche</a>の作者なのでその話、の前に生い立ちを軽く・・・という感じで話し始めたら少年時代にパソコンを自作している話がめちゃめちゃ面白くて、それだけで配信分を終えてしまったけど満足感がすごい。</p>
</dd>
<dt class="hdlist1"><a href="https://turingcomplete.fm/17">17. Gauche Schemeの基本デザインの選択理由、オブジェクトデータベース、浮動小数点数の落とし穴</a></dt>
<dd>
<p>今回はいよいよGaucheの話になって、一度決めたら変えるのが無理だったり非常に難しかったりするから言語の実装に入る前に決めておかないといけないこと、というのを三つ話してくれるんだけど、Lispとかオブジェクトデータベースとか途中浮動小数点数の扱いとかで色々脱線して、その脱線が長引いて今回も話し切らずに終了。脱線話もやっぱりめちゃめちゃ面白い。</p>
</dd>
<dt class="hdlist1"><a href="https://turingcomplete.fm/28">28. プログラミング言語のブートストラッピング問題、コードとの互換性を保ちつつ言語を変更していく話</a></dt>
<dd>
<p>これから聴く。楽しみだ。</p>
</dd>
</dl>
</div>
</div>
https://diary.kitaitimakoto.net/2019/02/03.html
Kinoppyが結構いい、 #tcfm 川合史朗さん回
2019-02-03T00:00:00Z
2019-02-03T00:00:00Z
<div class="paragraph">
<p>ちょっと、個人的にコミュニティサイト運用したくて、オープンソースのソーシャルネットワークアプリを探している。条件は</p>
</div>
<div class="ulist">
<ul>
<li>
<p>カスタマイズ可能</p>
</li>
<li>
<p>グループ(フォーラム)を作成可能</p>
</li>
<li>
<p>サイト外に見えるページも作れる</p>
</li>
<li>
<p>Ruby、Rust、Goあたり。Node.jsでもまあいいでしょう。カスタマイズが必要なので僕が使える言語か覚えたい言語がいい</p>
</li>
<li>
<p>できればActivityPub(完全に好み)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>という感じ。Twitterよりもmixiみたいなのを探している。</p>
</div>
<div class="paragraph">
<p>カスタマイズするつもりなので、必ずしもアプリケーションになっていなくてもよくて、例えばRailsエンジンでもいい。</p>
</div>
<div class="paragraph">
<p>実際に<a href="http://communityengine.org/">CommunityEngine</a>っていうRailsエンジンがあって、悪くなさそう。やや古い(Rails 4対応)のが気になるのと、折角だから新しいフレームワーク使ってみたいな、というくらいか。</p>
</div>
<div class="paragraph">
<p>Rust製でActivityPub対応を予定している<a href="https://www.aardwolf.social/">Aardwolf</a>っていうのも見付けたけど、動かしてみたら驚くほど何も出来ていなかった。これを採用するなら自分でどんどん作っていく必要がある。Rustのいい勉強になるかも知れない。</p>
</div>
<div class="paragraph">
<p><a href="https://diasporafoundation.org/">diaspora*</a>も気になっている(これもRails製だ)。期待できそうな気がしているからローカルで動かしてみるつもり。</p>
</div>
<div class="paragraph">
<p>GitHubの<a href="https://github.com/topics/social-network">social-network</a>トピックとか見ると色々あるんだけどあり過ぎて選べない・・・。おすすめあったら教えてください:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Mastodon:<a href="https://bookwor.ms/@KitaitiMakoto">@KitaitiMakoto</a></p>
</li>
<li>
<p>ツイッター:<a href="https://twittar.com/@KitaitiMakoto">@KitaitiMakoto</a></p>
</li>
</ul>
</div>
https://diary.kitaitimakoto.net/2019/02/02.html
オープンソースのソーシャルネットワークアプリを探している
2019-02-02T00:00:00Z
2019-02-02T00:00:00Z
<div class="paragraph">
<p><a href="https://www.aardwolf.social/">Aardwolf</a>っていうRustプログラムをビルドしようと、<a href="https://github.com/Aardwolf-Social/aardwolf/blob/master/INSTALL.md">公式のインストールドキュメント</a>に従ってやってみてもビルドが通らない。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% RUST_BACKTRACE=1 cargo run --bin setup
Compiling aardwolf v0.1.0 (/Users/ikeda/src/github.com/Aardwolf-Social/aardwolf)
error: failed to run custom build command for `aardwolf v0.1.0 (/Users/ikeda/src/github.com/Aardwolf-Social/aardwolf)`
process didn't exit successfully: `/Users/ikeda/src/github.com/Aardwolf-Social/aardwolf/target/debug/build/aardwolf-d833dd1a280ec7ae/build-script-build` (exit code: 101)
--- stdout
Feature selected: CARGO_FEATURE_SIMPLE_LOGGING
--- stderr
thread 'main' panicked at 'Couldn't compile translations: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/libcore/result.rs:997:5
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
at src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:39
1: std::sys_common::backtrace::_print
at src/libstd/sys_common/backtrace.rs:70
2: std::panicking::default_hook::{{closure}}
at src/libstd/sys_common/backtrace.rs:58
at src/libstd/panicking.rs:200
3: std::panicking::default_hook
at src/libstd/panicking.rs:215
4: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
at src/libstd/panicking.rs:478
5: std::panicking::continue_panic_fmt
at src/libstd/panicking.rs:385
6: std::panicking::try::do_call
at src/libstd/panicking.rs:312
7: core::char::methods::<impl char>::escape_debug
at src/libcore/panicking.rs:85
8: rocket_i18n::compile_po::{{closure}}
at /rustc/f29b4fbd742b8180edb5e06cad0977b08881541b/src/libcore/macros.rs:16
9: rocket_i18n::compile_po::{{closure}}
at /rustc/f29b4fbd742b8180edb5e06cad0977b08881541b/src/libcore/result.rs:825
10: rocket_i18n::update_po::{{closure}}
at /Users/ikeda/.cargo/registry/src/github.com-1ecc6299db9ec823/rocket_i18n-0.3.1/src/lib.rs:212
11: build_script_build::build_translations
at ./build.rs:18
12: build_script_build::main
at ./build.rs:12
13: std::rt::lang_start::{{closure}}
at /rustc/f29b4fbd742b8180edb5e06cad0977b08881541b/src/libstd/rt.rs:64
14: std::panicking::try::do_call
at src/libstd/rt.rs:49
at src/libstd/panicking.rs:297
15: panic_unwind::dwarf::eh::read_encoded_pointer
at src/libpanic_unwind/lib.rs:92
16: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
at src/libstd/panicking.rs:276
at src/libstd/panic.rs:388
at src/libstd/rt.rs:48
17: std::rt::lang_start
at /rustc/f29b4fbd742b8180edb5e06cad0977b08881541b/src/libstd/rt.rs:64
18: build_script_build::validate_features</pre>
</div>
</div>
<div class="paragraph">
<p>Rustに不慣れなのと、このリポジトリーじゃなくて依存リポジトリーの中でパニックを起こしていて何か自分には手が出ないような気がしてしまう。ググったりGitHubのイシューを覗いたりして見るけど手掛かりがなく諦めようかと思ったけど、今日同僚が「エラー見ても分からん」って言ったのに「そういう時はソースコード見るしかないですねえ」とか返しちゃった手前逃げ道もないのでコードを見た。プロジェクト直下の <code>./build.rs</code> は単に <code>rocket_i18n::compile_po</code> を呼んでいるだけで何もできることはなさそうなのでその先へ。 <code>.cargo/registry/src/github.com-1ecc6299db9ec823/rocket_i18n-0.3.1/src/lib.rs</code> って書いてあって「えっ、crates.ioからインストールしてるんじゃないの?」と訝しみつつGitHubを見てみたらAardwolf用にパッチを当てているようだ。しょうがないからチェックアウトしてきて該当ファイルの該当行へ。そしたら</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="rust"> <span class="nn">Command</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"msgfmt"</span><span class="p">)</span>
<span class="nf">.arg</span><span class="p">(</span><span class="nd">format!</span><span class="p">(</span><span class="s">"--output-file={}"</span><span class="p">,</span> <span class="n">mo_path</span><span class="nf">.to_str</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">()))</span>
<span class="nf">.arg</span><span class="p">(</span><span class="n">po_path</span><span class="p">)</span>
<span class="nf">.status</span><span class="p">()</span>
<span class="nf">.map</span><span class="p">(|</span><span class="n">s</span><span class="p">|</span> <span class="p">{</span>
<span class="k">if</span> <span class="o">!</span><span class="n">s</span><span class="nf">.success</span><span class="p">()</span> <span class="p">{</span>
<span class="nd">panic!</span><span class="p">(</span><span class="s">"Couldn't compile translations"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="nf">.expect</span><span class="p">(</span><span class="s">"Couldn't compile translations"</span><span class="p">);</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>これはRustよく分かんなくても分かる。 <code>msgfmt</code> なるコマンドを呼び出しているのだから、実際にそのコマンドだけ呼んでみたらヒントになるかも知れない。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% type msgfmt
msgfmt not found</pre>
</div>
</div>
<div class="paragraph">
<p>ない。ググると<a href="https://www.gnu.org/software/gettext/">gettext</a>のコマンドらしいことが分かる(i18nの文脈なので納得)。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% brew install gettext
Updating Homebrew...
Warning: gettext 0.19.8.1 is already installed and up-to-date
To reinstall 0.19.8.1, run `brew reinstall gettext`</pre>
</div>
</div>
<div class="paragraph">
<p>あれ? 入ってる? 何で呼べてないの? って思って更にググると、どうもHomebrewで入れるだけだとパスが通らないらしい。というわけで <code>.zshrc</code> でパスを通して動かす。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="zsh"><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/usr/local/opt/gettext/bin:<span class="nv">$PATH</span></code></pre>
</div>
</div>
<div class="literalblock">
<div class="content">
<pre>% source ~/.zshrc
% cargo run --bin setup
Compiling aardwolf v0.1.0 (/Users/ikeda/src/github.com/Aardwolf-Social/aardwolf)
Finished dev [unoptimized + debuginfo] target(s) in 4.16s
Running `target/debug/setup`
using database url `postgresql://aardwolf:p4ssw0rd@127.0.0.1:5432/aardwolf' to setup the aardwolf database
database migrations were successfully run, you're ready to go!
cargo run --bin setup 3.54s user 0.80s system 98% cpu 4.382 total</pre>
</div>
</div>
<div class="paragraph">
<p>動いた!</p>
</div>
https://diary.kitaitimakoto.net/2019/02/01.html
ビルドが通らない時はソースコードを読む
2019-02-01T00:00:00Z
2019-02-01T00:00:00Z
<div class="paragraph">
<p>日中は<a href="https://pptr.dev/">Puppeteer</a>と戯れていた。Canvasを使って描画しているやつって、headlessモードだと描画されないのかな? 原因ちゃんと調べてないけど、 <code>headless: false</code> だとスクショ取れるのに <code>true</code> だと取れない、というのに遭遇した。</p>
</div>
<div class="paragraph">
<p>ここ一年ぐらい、寝しなにポッドキャスト聴くのが好きで、今日は<a href="http://rebuild.fm/">Rebuild</a>のエピソード227<a href="http://rebuild.fm/227/">Your Request Doesn’t Spark Joy (kosamari)</a>(人生がときめかないあなたの要望)を聴く。kosamariさん本当にいろんなことを楽しそうに話してて聴いていて気持ちがいいなあ。<a href="https://github.com/WICG/virtual-scroller"><code><virtual-scroller></code></a>は名前聞くなあとは思ってたけどLayered APIの文脈だということ知らなくて、これ聴いて気になってソース読んだりし始めた。</p>
</div>
<div class="paragraph">
<p>その後basukeさんの<a href="http://rebuild.fm/225/">Condescending Pull Requests (basuke)</a>の回も聴き始めたのだけど、Edgeの話が終わった辺りで眠り込んでしまったみたいだ。こういう風に聴きながら入眠できるのがすごく好き。まんがとかだと目が疲れちゃうし、音楽だと(ちゃんと設定しないと)寝てる間中掛かり続けて眠りが浅くなったり起きてしまったりするけどポッドキャストは終わりがあるから邪魔されることもないし、あと、宮川さん達の声や雰囲気が心地いいので聴きながら眠るのはすごく気持ちがいい。</p>
</div>
<div class="paragraph">
<p>で、翌日(27日)は寝床でごろごろしながら入眠時まで巻き戻して続きを聞くという冬の朝を過ごすのだった。</p>
</div>
https://diary.kitaitimakoto.net/2019/01/26.html
Rebuild 227: Your Request Doesn't Spark Joy (kosamari)(人生がときめかないあなたの要望)を聴く
2019-01-26T00:00:00Z
2019-01-26T00:00:00Z
<div class="paragraph">
<p>Kubernetes上のポッド不安定問題、<a href="https://uptimerobot.com/">Uptime Robot</a>とか<a href="https://www.pingdom.com/">Pingdom</a>で監視してるけどその通知時刻とホストノードのアップタイムを比べて見るとどうもプリエンプティブノードを使ってるのが原因かなあ。</p>
</div>
https://diary.kitaitimakoto.net/2019/01/23.html
Kubernetes上のポッド不安定問題予想
2019-01-23T00:00:00Z
2019-01-23T00:00:00Z
<div class="paragraph">
<p>Kubernetes上のポッドが不安定なのようやく取り掛かることができたけど正直今見られるログ見ても全然分からんしStackDriverは有料なので各ポッドの条件を変えて対照実験することに。でも私生活はサイクルぐちゃぐちゃなので中々定期的に結果を見られないなあ。</p>
</div>
https://diary.kitaitimakoto.net/2019/01/22.html
Kubernetes上のポッド不安定問題に取り掛かる
2019-01-22T00:00:00Z
2019-01-22T00:00:00Z
<div class="paragraph">
<p><a href="https://nextwebconf.connpass.com/event/103056/">次世代 Web カンファレンス</a>に行って来ました。すごい楽しかった。開催ありがとうございました。</p>
</div>
<div class="paragraph">
<p>聞きながら取ったメモは<a href="https://scrapbox.io/apehuci/%E6%AC%A1%E4%B8%96%E4%BB%A3_Web_%E3%82%AB%E3%83%B3%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9">Scrapbox</a>に置いてあります。</p>
</div>
<div class="paragraph">
<p>聞いて来たのは以下の五つ。</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><a href="https://scrapbox.io/apehuci/%E6%AC%A1%E4%B8%96%E4%BB%A3_Web_%E3%82%AB%E3%83%B3%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9_%EF%BC%9A%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9">パフォーマンス</a></dt>
<dd>
<p>フォロントエンドのパフォーマンスの話。<a href="https://github.com/ampproject/worker-dom">worker-dom</a>っていうのに言及されてて、これ知らなかったので知れてよかった。</p>
</dd>
<dt class="hdlist1"><a href="https://scrapbox.io/apehuci/%E6%AC%A1%E4%B8%96%E4%BB%A3_Web_%E3%82%AB%E3%83%B3%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9_%EF%BC%9A%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B7%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3">アクセシビリティ</a></dt>
<dd>
<p>視覚障害者と美術館に行く体験が紹介されてて(<a href="https://note.mu/mjmjsachi/n/n43f833f29cdb">体験を共有することーー「明るい地上には あなたの姿が見える」</a>)、聞いてて興奮した。カンファレンスを通して、セッション後に講堂の外で登壇者と議論できる時間が取られているのだけど、このセッション後は昼休みだったのもあって50分ぐらい、まんがとその周辺に関するアクセシビリティについてお話しさせてもらった。贅沢。</p>
</dd>
<dt class="hdlist1"><a href="https://scrapbox.io/apehuci/%E6%AC%A1%E4%B8%96%E4%BB%A3_Web_%E3%82%AB%E3%83%B3%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9_%EF%BC%9A%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">マイクロサービス</a></dt>
<dd>
<p>新しい何かを知るというよりは、第一線の人達の肌感を知れるという点でよかった。</p>
</dd>
<dt class="hdlist1"><a href="https://scrapbox.io/apehuci/%E6%AC%A1%E4%B8%96%E4%BB%A3_Web_%E3%82%AB%E3%83%B3%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9_%EF%BC%9AAuthN%2FZ">AuthN/Z</a></dt>
<dd>
<p>最近、ウェブ上でのアイデンティティについてあれこれ考えていたので、自分の状況にうまくハマって面白かった。セッション後に話を聞こうと思ったけど時間取れず断念。</p>
</dd>
<dt class="hdlist1"><a href="https://scrapbox.io/apehuci/%E6%AC%A1%E4%B8%96%E4%BB%A3_Web_%E3%82%AB%E3%83%B3%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9_%EF%BC%9A%E5%BA%83%E5%91%8A">広告</a></dt>
<dd>
<p>アドテクその物の話よりは、技術的な知識を前提とした、広告業界の最近の事情や今後の進みそうな方向の話だった。これも面白かったなあ。</p>
</dd>
</dl>
</div>
https://diary.kitaitimakoto.net/2019/01/13.html
次世代 Web カンファレンス
2019-01-13T00:00:00Z
2019-01-13T00:00:00Z
<div class="paragraph">
<p>思い付きで作りたい物が出来てcanvasのライブラリーを物色。<a href="https://www.createjs.com/">CreateJS</a>(Adobe Animate CCからJavaScriptを書き出した時に使われるらしいライブラリー)と<a href="https://phaser.io/">Phaser</a>(Pixi.jsを使ったゲームエンジン)は使ったことがあったので、できればこれ以外で、使ったの何年も前だから尚更他の新しいのがよかろう、という感じでいいのがないか物色した。</p>
</div>
<div class="paragraph">
<p>パフォーマンスとかAPIとかじゃなくてついついデモをみておっと思ったのに惹かれてしまうのだけど、その点で一番よかったのが<a href="http://paperjs.org/">Paper.js</a>だった(そう言えばこれも昔ちょっと触ったことあったな)。しかし、インストールして触ろうと思うとES5でしか書けないので辛く、それはまあいいとしても(Google Apps Scriptとかそうだしね、おじさんだからES5慣れてますよ?)、それくらい古いライブラリーは、流石にないかな、と思ってやめることにしました。</p>
</div>
<div class="paragraph">
<p>で、最終的に<a href="http://fabricjs.com/">Fabric.js</a>を使うことにしました。ちょっとドキュメント読んでます。</p>
</div>
<div class="paragraph">
<p>そう言えば色々探している間に<a href="https://ics.media/tutorial-createjs/index.html">CreateJS入門サイト</a>なるものが出来ていた。昔の自分に見せてやりたい。</p>
</div>
https://diary.kitaitimakoto.net/2019/01/05.html
canvasライブラリー
2019-01-05T00:00:00Z
2019-01-05T00:00:00Z
<div class="sect2">
<h3 id="_turbolinks導入">Turbolinks導入</h3>
<div class="paragraph">
<p>この日記は静的サイトジェネレーターの<a href="https://middlemanapp.com/">Middleman</a>で作っているのだけど、自前でpjax(らしき物)を実装して回遊を速くしていた(<a href="../../2017/01/10.html">Polymerでpjaxする、またはapp-locationの使い方</a>)。</p>
</div>
<div class="paragraph">
<p>けど、前々から「<a href="https://github.com/turbolinks/turbolinks">Turbolinks</a>だけRailsから引き剥がして入れてみたらそれでいいのでは?」とは思ってて、年始のお休み時期を利用してやってみた。今はRubyなしでNPMだけで導入できるんですね。</p>
</div>
</div>
<div class="sect2">
<h3 id="_codegridを読む">CodeGridを読む</h3>
<div class="paragraph">
<p>同じく年始の休みを利用してウェブ積ん読を消化しているのだけど、<a href="https://app.codegrid.net/">CodeGrid</a>の記事も読みました。</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><a href="https://app.codegrid.net/entry/2018-round-up-1">行く2018年、来る2019年 前編 薄いWeb Componentsと厚いライブラリ</a></dt>
<dd>
<p>毎年恒例のPixelGrid社員の座談会。テーマにWebコンポーネントが選ばれてるの胸熱過ぎる。「フレームワークとウェブコンポーネントは別のレイヤー」という見解はなるほどと思いました。</p>
</dd>
<dt class="hdlist1"><a href="https://app.codegrid.net/entry/2018-accessibility-1">2018年のWebアクセシビリティを振り返る 前編 WCAG 2.1の勧告</a></dt>
<dd>
<p>『<a href="https://tatsu-zine.com/books/designing-web-accessibility">デザイニングWebアクセシビリティ</a>』(著)、『<a href="https://tatsu-zine.com/books/coding-web-accessibility">コーディングWebアクセシビリティ</a>』(訳)、『<a href="https://tatsu-zine.com/books/inclusive-html-css-javascript">インクルーシブHTML+CSS & JavaScript</a>』(訳)のあの太田良典さんの記事。「 <code>img</code> の <code>alt</code> 属性が欠けていることでアップルが訴訟を起こされた」というの、衝撃……。</p>
</dd>
</dl>
</div>
</div>
https://diary.kitaitimakoto.net/2019/01/04.html
静的サイトにTurbolinks、CodeGridの年末記事
2019-01-04T00:00:00Z
2019-01-04T00:00:00Z
<div class="paragraph">
<p>Web Speech APIのSynthesis(合成音声での読み上げ)使ってて、読み上げがが終わった(止まった)時にイベントが発生するのだけどそれが最後まで読み終わった物なのかキャンセルされた物なのか知りたい。と思って仕様を調べたら、読み終わった場合は <code>end</code> イベントが発生して、キャンセルされた場合はエラーになるのでそれにコールバックを登録すればよさそう。に見えるのだけどやってみるとうまくいかない。キャンセル時にエラーになってくれない。これはブラウザーのバグ? てかそもそもまだドラフト段階だから実装後に仕様が変わったのかな、と思ってBugZillaを見てみたら案の定だった。詳しくはこちらをどうぞ:<br>
<a href="https://scrapbox.io/apehuci/Web_Speech_Synthesis_Utterance%E3%81%8C%E6%99%AE%E9%80%9A%E3%81%AB%E7%B5%82%E3%82%8F%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%8Bcancel()%E3%81%A7%E6%AD%A2%E3%81%BE%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%8B%E7%9F%A5%E3%82%8A%E3%81%9F%E3%81%84">Web Speech Synthesis Utteranceが普通に終わったのかcancel()で止まったのか知りたい</a></p>
</div>
https://diary.kitaitimakoto.net/2019/01/02.html
Web Speech APIのバグ
2019-01-02T00:00:00Z
2019-01-02T00:00:00Z
<div class="paragraph">
<p><a href="https://pwa-starter-kit.polymer-project.org/">PWA Starter Kit</a>やってみたらしれっと<a href="https://redux.js.org/">Redux</a>使ってて、Redux復習しておいてよかったなってなりました。<a href="https://lit-element.polymer-project.org/">LitElement</a>に乗り換えているところと言い、このキットは変化が早いですね。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/28.html
PWA Starter Kitの変化
2018-12-28T00:00:00Z
2018-12-28T00:00:00Z
<div class="paragraph">
<p><a href="https://www.ruby-lang.org/">Ruby</a>の新バージョンがリリースされましたね。Congrats!</p>
</div>
https://diary.kitaitimakoto.net/2018/12/25.html
Ruby 2.6リリース
2018-12-25T00:00:00Z
2018-12-25T00:00:00Z
<div class="paragraph">
<p>Amazon Echo Showが出たので買ってオフィスに置いているんですが、「アレクサ」って呼びかけると別の人のEchoが反応してしまうのでウェイクワードを「エコー」に変えました。エコーの他は「アマゾン」と「コンピュータ」が選べるらしい。「コンピュータ」やばいすね。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/21.html
Echoの名前
2018-12-21T00:00:00Z
2018-12-21T00:00:00Z
<div class="paragraph">
<p>『<a href="http://morningmanga.com/blog/poten">ポテン生活</a>』(木下晋也)と『&』(おかざき真里)を交互に読んでいるんですが『ポテン』がすごくて、『ポテン』を何十ページか読んだ後に『&』を読んでいると、ページが左下に近くに連れて落ちを期待してしまうんですよね、で、慌てて「違う違う、そういうまんがじゃない今読んでるのは、もっとしんどいやつ」ってなる。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/20.html
『ポテン生活』と『&』
2018-12-20T00:00:00Z
2018-12-20T00:00:00Z
<div class="paragraph">
<p><a href="https://redux.js.org/">Redux</a>を復習しているんですがフレームワークってこうだよなって感じています。単に定形的な作業をまとめてやらなくていいようにしているのではなくて、問題を分割して一度に一つのことに集中できるようにしている。便利なんじゃなくて考え方を変えさせてくる。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/19.html
Reduxを復習してフレームワークを感じる
2018-12-19T00:00:00Z
2018-12-19T00:00:00Z
<div class="paragraph">
<p>知人に連れられて古本屋に行って来たのですが一畳半ぐらいのギャラリースペースがあって個展が開かれていました。その個展いいなと思いつつ、いいということを表現するボキャブラリーがないので知人と話が弾むことなく黙って見ているだけでした。黙って見ているの好きですけど、ボキャブラリー自体はあった方がよさそう。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/18.html
「いい」ことのボキャブラリー
2018-12-18T00:00:00Z
2018-12-18T00:00:00Z
<div class="paragraph">
<p>「<a href="https://rust.connpass.com/event/111221/">Rust入門者向けハンズオン #5</a>」に行って来たのだけどよかったです。積極的にプログラムがうまく動かない状況を作っていって「これって何がおかしいんですかねえ」「どうしたらいいんですかねえ」ってメンターの人に聞いていくとブログとかでは伝えるのが難しいデバッグのテクニック教えてもらったりとか、僕が何に手間取っているのか見た上でアドバイスくれたりとかですごく効率よく学習できました。プログラミング覚える時に難しいのは(と言うか他のもそうだろうけど)、動く物を作ることではなくて、どうやってうまくいかない時に一人で調べたり色々試したりすることができるようになれるか、ということで、そこまで引っ張って行ってくれるメンターがいるというのは大変幸運なことです。そこに達すればあとは一人でなんとかなる(ことが多い)。</p>
</div>
<div class="paragraph">
<p>教えてもらったデバッギングに感激してしまったのでRust書きたくなっていて、ちょうど買ったAmazon Echo Show(画面付きのAlexa、Primeビデオとか見れるやつ)も届いたし、Alexaの追加スキルとかRustで開発したい。「Alexa、おはよう」って言ったら職場の勤怠システムに打刻するようにさせたい。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/15.html
Rust入門者向けハンズオン
2018-12-15T00:00:00Z
2018-12-15T00:00:00Z
<div class="paragraph">
<p>このニュースを見て「きたな」と思いました。</p>
</div>
<div class="paragraph">
<p>■Google Kubernetes Engineがサービスメッシュ「Istio」を統合、マネージドサービスを提供開始<br>
<a href="https://www.publickey1.jp/blog/18/google_kubernetes_engineistio.html" class="bare">https://www.publickey1.jp/blog/18/google_kubernetes_engineistio.html</a></p>
</div>
<div class="paragraph">
<p>インフラを<a href="https://kubernetes.io/">Kubernetes</a>に移行して、全然大したことをしていないけど手を動かすことでKubernetesとか<a href="https://cloud.google.com/kubernetes-engine/">GKE</a>とかに関するニュース感度が高くなっているようです。手を動かすって大事。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/14.html
手を動かすことと情報感度
2018-12-14T00:00:00Z
2018-12-14T00:00:00Z
<div class="paragraph">
<p>数年ぶりに<a href="https://developer.mozilla.org/docs/Web/API/Web_Speech_API">Web Speech API</a>を調べたらFirefoxでも使えるようになっていました。「このAPI楽しいけど世間の関心薄そうだしChromeだけで入っててそのうち消えるのかなあ」って感じていたので嬉しい。このまま広まるんだろうか。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/13.html
Web Speech API
2018-12-13T00:00:00Z
2018-12-13T00:00:00Z
<div class="paragraph">
<p><a href="https://www.doxsey.net/blog/kubernetes--the-surprisingly-affordable-platform-for-personal-projects">Kubernetes: The Surprisingly Affordable Platform for Personal Projects</a>を読みながら個人サイト(群)のインフラを<a href="https://kubernetes.io/">Kubernetes</a>に移してみたんですがちょいちょい落ちるようになって多い時には一日に二十回ぐらい。前は落ちたら落ちっぱなしだったところ、数分で自動で復旧するようになったので手が掛からずそこはいいんですが、落ち過ぎです。</p>
</div>
<div class="paragraph">
<p><a href="https://cloud.google.com/kubernetes-engine/">GKE</a>でノードを作る時にプリエンプティブインスタンスを選んだからそのせいなのか(でも三台のインスタンスが同時に落ちたりするもんなんだろうか)、アプリの作りに問題があるのか……。</p>
</div>
<div class="paragraph">
<p>「推測するな、計測せよ」の金言の通り、今あれこれ考えるの意味なくて、ログを取れるようにしたりメトリクスを細かく見られるようにしたりしてからようやく考えることなのだけど、時間と……お金が。年明けにお年玉を突っ込んで調べたいところ。</p>
</div>
https://diary.kitaitimakoto.net/2018/12/11.html
Kubernetesを使い始めてみた、が……
2018-12-11T00:00:00Z
2018-12-11T00:00:00Z
<p>今読んでいる小説にこんな場面が出てきた。四、五十代の四人のお盆の日のこと。</p>
<blockquote style="font-family: serif;">
「英樹、少しは落ち着いてまわりの景色でも眺めなさいよ」<br>
と言うと、<br>
「お墓参りに来たんで、景色見に来たんじゃない」<br>
と言い返した。<br>
「やだねえ、せっかちな弟を持つと」<br>
と言って、奈緒子姉は両側にほぼ等間隔に<ruby>欅<rp>(</rp><rt>けやき</rt><rp>)</rp></ruby>が植えられた道の車道に出て、日傘をさしてこれ見よがしにゆっくりと歩いて見せた。道には中央分離帯まであって、ツツジらしい低木が植えられていて、そこらじゅうというか風景全体から<ruby>蝉<rp>(</rp><rt>せみ</rt><rp>)</rp></ruby>の声が聞こえていた。正門のところでは車も人もかなり多かったけれど、広い敷地の霊園全体に散っていったのでここまで来るとまばらになっていて、車道を歩いても車にひかれる心配はなかった。幸子姉は日傘をささなくても<em class="kenten">つば</em>のやたらに広い帽子で背中に大きな影ができていたが、まわりを見るときには広い<em class="kenten">つば</em>がかえってじゃまで、手で<em class="kenten">つば</em>をめくってまわりを見回しながら、<br>
「何度来てもいいところだよね、ここは」<br>
と言った。<br>
「いいところ?<br>
死んだらどこだって同じことだ」<br>
英樹兄は一段分高くなっているお墓の敷地に一人でさっさと入り、石<ruby>灯籠<rp>(</rp><rt>どうろう</rt><rp>)</rp></ruby>やその手前に植えられた<ruby>沈丁花<rp>(</rp><rt>じんちょうげ</rt><rp>)</rp></ruby>と、もう一本はツゲだと思うが、それを確かめるように一つ一つ見ていた。<br>
「七月にわたしと清人で掃除したからきれいでしょ」奈緒子姉が言った。<br>
「おじいちゃんが、『ここがいい』って言ってたんだから、『いいところだ』って言ってあげなよ」<br>
「何でもケチつけるところもお父さんにそっくりだよね。『ここがいい』って言って、自分でお墓まで建てといたんだから、『いいところ』でいいじゃないのよ」<br>
「おじいちゃん」も「お父さん」も伯父のことだ。この霊園は一区画ずつが広くて、特に伯父と伯母のお墓のあるこのあたりは最近の建て売り住宅の狭い庭ぐらいの広さがあって、ゆったりしている。敷地の入り口の両側に今英樹兄が見ていた沈丁花とツゲか何かが植えられていて、その奥に石灯籠が立っていて、奥の正面は当然墓石で、その右奥に二メートルくらいの<ruby>椿<rp>(</rp><rt>つばき</rt><rp>)</rp></ruby>があって、左の奥にはそれよりもっと高い<ruby>楓<rp>(</rp><rt>かえで</rt><rp>)</rp></ruby>が枝を広げている。この時期に葉が赤くなる種類だから華やかというかまわりを明るくしているみたいだった。<br>
「死んで薄暗いところに埋められるのは嫌だけど、こういうところだったらいいじゃないの」<br>
「死んだらどこだって同じことだ」<br>
英樹兄はさっきと同じことを言って、タワシに水をつけて墓石をごしごし洗い始めたが、奈緒子姉も幸子姉もこれは英樹兄の仕事と割り切っているらしく、手伝う素振りをまったく見せずにまだまわりを歩き回っていた。<br>
私がここに来るのは二年前の伯母の葬式のとき以来で、あのときの印象では霊園全体にあまり高い木がなくて眺望が開けていて、青い空の色が何も障害物なしにずうっと下の低木の緑にまで降りているという感じだと思っていたけれど、道沿いの欅だけではなくて、墓地のあちらこちらに高く伸びて葉を茂らせている欅や桜や松や杉のせいで案外、遠くまで見渡せるようになっていなかった。あれは離れた区画まで歩いて行ったときの印象だったのかもしれないと思った。このあたりは気の多い公園という感じだった。<br>
「英樹、もうそのくらいでいいわよ」<br>
そろそろ待ちきれなくなったらしい奈緒子姉が言った。結局奈緒子姉だってゆっくりまわりを見ているなんてできないのだが、それだけではなくて蚊も飛んでいて、奈緒子姉は<ruby>肘<rp>(</rp><rt>ひじ</rt><rp>)</rp></ruby>のあたりを<ruby>掻<rp>(</rp><rt>か</rt><rp>)</rp></ruby>き、幸子姉は右足の<ruby>踵<rp>(</rp><rt>かかと</rt><rp>)</rp></ruby>で向こう<ruby>脛<rp>(</rp><rt>ずね</rt><rp>)</rp></ruby>のあたりをこすっていた。<br>
「英樹、そんなにごしごしやったら<ruby>摩<rp>(</rp><rt>す</rt><rp>)</rp></ruby>り減っちゃって、あんたが入るときまでもたないわよ」<br>
「バカ言え」<br>
と言ったけれど、それで英樹兄はやめて「きれい、きれい」と言って墓石のてっぺんをぺたぺたと叩いて、それから<ruby>脇<rp>(</rp><rt>わき</rt><rp>)</rp></ruby>に立っている黒い石碑を洗いはじめた。石碑には「墓誌」と刻まれていて、つまりは伯父と伯母の戒名と<ruby>享年<rp>(</rp><rt>きょうねん</rt><rp>)</rp></ruby>が彫られているのだが、<br>
「兄ちゃんもあと二十年で仲間入りだね」<br>
と言った幸子姉の冗談に英樹兄は何も言い返さなかった。蝉の声にかき消されて聞こえなかったのかもしれない。奈緒子姉は「さあ、さ」と言って、花の入っている<ruby>手桶<rp>(</rp><rt>ておけ</rt><rp>)</rp></ruby>を持って一段分高いお墓の敷地に入っていき、<br>
「もうお花あげちゃうわよ」<br>
と言ったが、言い終わる前に花立てに差しはじめていて、英樹兄は、<br>
「じゃあ高志、線香に火をつけろ」<br>
と、墓誌の石を洗いながら言い、私がお墓の敷地の境界の石の隅に<ruby>屈<rp>(</rp><rt>かが</rt><rp>)</rp></ruby>んで紙を燃やして線香の束に火をつけはじめていると、腕のまわりに蚊が飛んできて、私が払おうとしたら幸子姉がパチンと叩いて殺した。それで私の頭の辺に日陰ができたのが幸子姉の帽子かと思ってちらっと見上げると奈緒子姉の日傘で、奈緒子姉が敷地の内側から身を乗り出すように<ruby>覗<rp>(</rp><rt>のぞ</rt><rp>)</rp></ruby>き込んでいて、<br>
「ちゃんとつけられる?」<br>
と言うと、「高志も大人だよ」と、幸子姉が笑ったのだけれど、私がこういうことは何もできないかものすごく苦手だと思っている奈緒子姉はあらためて感心したように「早いもんだねえ」と言った。<br>
「『なおこねえちゃあん』って、泣いてた子どもがもう四十<span class="mdash">—</span>、いくつだっけ?」<br>
「十月で四」<br>
「じゃあ、わたしとちょうど十歳違いじゃない」<br>
「そうだよ。生まれたときから、十歳違いだよ」<br>
「ちゃんと考えたことなかったわ。<br>
ついた?」<br>
「もうちょっと」と私は言った。<br>
「わたしがおしゃべりして、時間稼ぎしてあげてるんだから、早くつけなさいよ。<br>
<span class="mdash">—</span>でもここだって、夜はやっぱり怖いんでしょうね」<br>
「『いいところ』って言ってみたり、『怖い』って言ってみたり」<br>
向こうから英樹兄が言った。<br>
「いいところだって、怖いものは怖いじゃないの。ま、どっちにしろわたしが入るのはここじゃないけど」<br>
「だから死んだらどこだって同じことだって、言ってるじゃないか」
</blockquote>
<p>これを僕は優しさだと思った。反応的な優しさではなくて、英樹に組み込まれた優しさだと思った。奈緒子の寂しさを感じ取って英樹が優しさからこれを言ったのではない、と思う。そもそも、奈緒子がに寂しさがあるのかどうか全然分からない。あると言われれば納得するし、ないと言われても納得する。英樹が勝手に寂しさを感じたかどうかも同様に分からないけど、奈緒子が寂しく思っていることよりはそっちの方があり得る話だとは思う。でも、僕はそうですらないと思う。それでも、これは優しさだと感じた。</p>
<p>みんなはどう思うんだろう。</p>
<p>これを優しさだと思わない人はどういう人だろう。僕がここに優しさを見い出すのは、お前の優しさがそういう形をしているからだろう、と言われれば、それは、そうだと答える。でも、だからこれはお前とお前のような人にとってだけの優しさなのだ、と言われるとすると、そうではないと言いたい。これは英樹から万人への優しさなのだと感じる。</p>
<p>仮定が多過ぎてこの引用から優しさについて話をすることはできない、という人もいそうだ。全くその通りだと思う。だからそういう人とは話さないで大丈夫。(というのを、否定的なニュアンスを入れずに書くにはどうすれば……。)</p>
<p>ストローマンいっぱいで、気持ち悪い文になっちゃいましたね。反省。(しかし書き直さない。)</p>
<p>引用は保坂和志『<a href="http://www.kawade.co.jp/np/isbn/9784309414225/">カンバセイション・ピース</a>』(河出文庫)によりました。ありがとうございました。平野敬子の装幀が好き。</p>
<h2>余談</h2>
<p>職場では優しさをFFSの受容性と結び付ける論調が広まっていて、と言っても一人が主張しているだけなんだけど僕はその人とよく話すから職場全体がそういう論調なのだというように感じていて、受容性の低い僕はそれだけじゃないと主張したい、というのがマトリクスになっていたから引用箇所が僕の心に届いたのだろうなと思います。</p>
https://diary.kitaitimakoto.net/2018/08/25.html
組み込まれた優しさ
2018-08-25T00:00:00Z
2018-08-25T00:00:00Z
<div class="paragraph">
<p>O’Reilly Japanから出ている『 <a href="https://www.oreilly.co.jp/books/9784873118406/">入門 Kubernetes</a> 』のコード部分(Kubernetesなので、殆どがYAML)をシンタックスハイライトして読めるようにしてみた。</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://gyazo.com/e4a69651ff59791ef9165283b7e3fe80.png" alt="『入門 Kubernetesのコードがシンタックスハイライトされている』" width="600">
</div>
</div>
<div class="paragraph">
<p><a href="https://www.oreilly.co.jp/ebook/">O’Reilly Japan Ebook Store</a>から買うとEPUBファイルをダウンロードできるので、それに対して<a href="https://gitlab.com/KitaitiMakoto/pirka">Pirka</a>というコマンドラインツールを使うと、コード部分をハイライトしてくれる。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% gem install pirka
% pirka update
% pirka ./入門Kubernetes.epub</pre>
</div>
</div>
<div class="paragraph">
<p>Pirkaでは、本ごとに「本の中のどの部分がソースコードで、その言語は何か」という辞書を持っていて、その辞書はGitリポジトリーになっている。そのリポジトリーに『入門 Kubernetes』の分を追加した、ということだ。</p>
</div>
<div class="paragraph">
<p>リポジトリーはこちら: <a href="https://gitlab.com/KitaitiMakoto/pirka-library" class="bare">https://gitlab.com/KitaitiMakoto/pirka-library</a></p>
</div>
<div class="paragraph">
<p>ほんと、EPUBファイルをダウンロードさせてくれるのはありがたいです。</p>
</div>
<div class="paragraph">
<p>数は少ないけど、他にもこんな本の辞書を登録してある。</p>
</div>
<div class="literalblock">
<div class="content">
<pre>% pirka lib | grep title
title: WEB+DB PRESS plusシリーズ APIデザインケーススタディ Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方
title: WEB+DB PRESS plusシリーズ nginx実践入門
title: オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
title: Serverspec
title: 入門Kubernetes</pre>
</div>
</div>
https://diary.kitaitimakoto.net/2018/07/03.html
『入門 Kubernetes』をシンタックスハイライトして読む
2018-07-03T00:00:00Z
2018-07-03T00:00:00Z
<div class="paragraph">
<p>プロジェクトが依存しているライブラリーをチェックして、バージョンが古いとか、セキュリティホールが見付かっているとかの警告をしてくれるGemnasiumというサービスがあったのだけど、先日シャットダウンした。GemnasiumはGitLab社に買われ、チームはGitLabの依存性検査機能をやっているらしい( <a href="https://docs.gitlab.com/ee/user/project/import/gemnasium.html" class="bare">https://docs.gitlab.com/ee/user/project/import/gemnasium.html</a> )。というわけで、「GitLabに移行してね」というメールが来ていたので、移行してみた。</p>
</div>
<div class="paragraph">
<p>移行手順はGitLabのドキュメント( <a href="https://docs.gitlab.com/ee/user/project/import/gemnasium.html#migrating-to-gitlab">Migrating to GitLab</a>)に従って <code>.gitlab-ci.yml</code> ファイルを編集するだけ。</p>
</div>
<div class="paragraph">
<p>ただ、Gemnasiumの時は、(Rubyの場合) <code>Gemfile.lock</code> のないリポジトリー(ライブラリーとか)でも依存性検査をしてくれていたのだけど、GitLabではそうはなっていなくて、残念。</p>
</div>
https://diary.kitaitimakoto.net/2018/05/28.html
GemnasiumからGitLabの依存性検査へ
2018-05-28T00:00:00Z
2018-05-28T00:00:00Z
<div class="paragraph">
<p><a href="https://kitaitimakoto.gitlab.io/epub-parser/">EPUB Parser</a>の<a href="https://kitaitimakoto.gitlab.io/epub-parser/file.CHANGELOG.html#_0_3_7">v0.3.7</a>をリリースした。</p>
</div>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">epub-parser (0.3.7): Parse EPUB 3 book loosely <a href="https://t.co/7UsY7J4lN1">https://t.co/7UsY7J4lN1</a></p>— RubyGems (@rubygems) <a href="https://twitter.com/rubygems/status/992937809936777216?ref_src=twsrc%5Etfw">2018年5月6日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<div class="paragraph">
<p>二か月ぶりですね。</p>
</div>
<div id="toc" class="toc">
<div id="toctitle" class="title">目次</div>
<ul class="sectlevel1">
<li><a href="#2018-05-06-epub2-cover-image">EPUB 2の表紙画像対応</a></li>
<li><a href="#2018-05-06-command-to-extract-cover-image">表紙画像を抜き出すコマンド</a></li>
<li><a href="#2018-05-06-change-homepage">ホームページの変更</a></li>
</ul>
</div>
<div class="sect1">
<h2 id="2018-05-06-epub2-cover-image">EPUB 2の表紙画像対応</h2>
<div class="sectionbody">
<div class="paragraph">
<p>たまにEPUB Parserをフォークしているのを見掛けて、その中で結構、EPUB 2のやり方で表紙を抜き出すパッチを当てているのがあるので、対応することにした。</p>
</div>
<div class="paragraph">
<p>EPUB仕様のバージョンは、日本では大体3系を使っていると思うんだけど、英語圏ではまだ2系を見る(O’Reillyの本とか)。EPUB 3の一つの特徴が日本語を含むCJKの慣習に寄ったことで、ルビや縦書きが導入された。<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>逆に言うと英語圏ではこれまでのツールやワークフローを変更する嬉しさがないわけで、EPUB 2が現役なんだろう。</p>
</div>
<div class="paragraph">
<p>CJKとは関係ないけど、EPUB 3になって表紙画像の指定方法も変わっている(というか今まではっきりしていなかったのをはっきりさせた)。そんなわけで、表紙画像を参照できそうな <code>#cover_image</code> というメソッドが、手元の(EPUB 2の)本では <code>nil</code> を返してしまうのをなんとかしたくてパッチを当てていたのだろうと思う。EPUB ParserはEPUB 3の為のライブラリーなので長いこと無視していたけど、状況が一向に変わらないようなので対応した。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="2018-05-06-command-to-extract-cover-image">表紙画像を抜き出すコマンド</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://kitaitimakoto.gitlab.io/epub-parser/file.EpubCover.html">EPUBファイルの中から表紙画像を抜き出すをコマンド</a>を追加した。</p>
</div>
<div class="listingblock">
<div class="content">
<pre>% epub-cover APIデザインケーススタディ-――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方_00.epub
Cover image output to cover.jpg</pre>
</div>
</div>
<div class="paragraph">
<p>使いでがあるようなないような。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="2018-05-06-change-homepage">ホームページの変更</h2>
<div class="sectionbody">
<div class="paragraph">
<p>これまで<a href="http://rubydoc.info/gems/epub-parser/">rubydoc.infoのページ</a>をホームページにしていたのだけど、<a href="https://kitaitimakoto.gitlab.io/epub-parser/file.Home.html">GitLab Pages</a>に変更した。</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="https://gitlab.com/KitaitiMakoto/epub-parser/blob/master/docs/yard-forwardable_def_delegators_handler.rb">YARDのカスタマイズ</a>を反映させたかった</p>
</li>
<li>
<p>YARDのドキュメントに外部ファイルのサンプルコードを埋め込みたかった</p>
<div class="ulist">
<ul>
<li>
<p>例: <a href="https://kitaitimakoto.gitlab.io/epub-parser/file.AggregateContentsFromWeb.html" class="bare">https://kitaitimakoto.gitlab.io/epub-parser/file.AggregateContentsFromWeb.html</a></p>
</li>
</ul>
</div>
</li>
<li>
<p><a href="https://kitaitimakoto.gitlab.io/epub-parser/coverage/">コードカバレッジ</a>も載せたかった</p>
<div class="ulist">
<ul>
<li>
<p><a href="https://coveralls.io/">Coveralls</a>は無料プランだとGitLabに対応していない……</p>
</li>
<li>
<p><a href="https://codecov.io/">Codecov</a>はGitLabのリポジトリーが何故か取得できない……</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="paragraph">
<p>あたりを解決するのに、<a href="https://about.gitlab.com/features/gitlab-ci-cd/">GitLab CI</a>を使ってビルドして、GitLab Pagesに上げることにした。カスタムドメインで運用したいとかあるんだけど取り敢えず今回はここまで。</p>
</div>
<div class="paragraph">
<p>あとはバグ直したりMarkdownからAsciiDocに移行したりドキュメントを修正したりしてた。</p>
</div>
</div>
</div>
<div id="footnotes">
<hr>
<div class="footnote" id="_footnotedef_1">
<a href="#_footnoteref_1">1</a>. 個人的には、ビデオやSVGなんかのメディア対応が一番大きいと思ってる。
</div>
</div>
https://diary.kitaitimakoto.net/2018/05/06.html
EPUB Parser v0.3.7リリース
2018-05-06T00:00:00Z
2018-05-06T00:00:00Z
<div class="paragraph">
<p>この前の「<a href="../../2018/05/01.html">Rocketでチャネルをうまく扱いたい</a>」には少し反応を貰えました。ありがとうございます。</p>
</div>
<iframe src="https://mastodon.home.js4.in/@hcm/99953290260137403/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400"></iframe><script src="https://mastodon.home.js4.in/embed.js" async="async"></script>
<div class="paragraph">
<p>確かに! すごい! よく気付きましたね!!</p>
</div>
<iframe src="https://gs.yvt.jp/@8vit/99953768231417225/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400"></iframe><script src="https://gs.yvt.jp/embed.js" async="async"></script>
<div class="paragraph">
<p>これは、<a href="https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html"><code>mpsc::channel</code> のドキュメント</a>や<a href="https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html">`mpsc::Sender`のAPIリファレンス</a>読んで分かってるつもりだったのですが、指摘されると考えが進みますね。ありがたや。この前の日記では、自分でも何に釈然としていないのか分からない状態で書いていたのだなあ、ということがよく分かりました。</p>
</div>
<div class="paragraph">
<p>ポイントはこうです。</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>全部自分で書けるなら</strong>、<code>Sender</code> を <code>clone()</code> して、ウェブリクエストを処理する各スレッドに渡すことができる</p>
</li>
<li>
<p><a href="https://rocket.rs/">Rocket</a>の<a href="https://api.rocket.rs/rocket/struct.State.html"><code>State</code></a>はパラメーターに <code>Send</code> と <code>Sync</code> を要求する(が、 <code>Sender</code> は <code>Sync</code> を実装しない)</p>
<div class="ulist">
<ul>
<li>
<p>これは多分、一般にマルチスレッド対応させるため、つまり複数のワーカー間で状態(ここでは <code>Sender</code> )を共有するため</p>
</li>
</ul>
</div>
</li>
<li>
<p>Rocketがスレッドを作るところに割り込むことはできない</p>
<div class="ulist">
<ul>
<li>
<p>ので <code>sender.clone()</code> してスレッドに渡すことができない</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="paragraph">
<p>ので、「(全部自分で書ければ)理論上は <code>clone</code> できるのになあ」という気持ちと「まあ一般的に作るフレームワークの立場としては最初からマルチスレッド対応になっている構造体を要求せざるを得ないよね」という気持ちとがぶつかってもやもやしていたのでありました。あーすっきりした。</p>
</div>
<div class="paragraph">
<p>最後の「Rocketがスレッドを作るところに割り込むことはできない」のはちょっと重たくて、実はRocketではなくて、Rocketが使うHTTPライブラリーの<a href="https://hyper.rs/">hyper</a>のレイヤーの話なので(<a href="https://github.com/hyperium/hyper/blob/v0.10.13/src/server/listener.rs#L31">server/listener.rs#L31</a>)Rocketに求めるのはちょっと厳しいなという気がします。特に、hyperを使わなくなることも検討されているようですし(<a href="https://github.com/SergioBenitez/Rocket/issues/17">Stabilize HTTP library · Issue #17</a>)。</p>
</div>
<div class="ulist">
<ul>
<li>
<p>自動で <code>clone</code> を呼ぶようなラッパー構造体を作って、</p>
</li>
<li>
<p>hyperでスレッドに変数をムーブする時に呼び出される「前処理フック」用のトレイトを作って、</p>
</li>
<li>
<p>ラッパー構造体でそのトレイトを実装させる</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>とかやればうまくいくんですかねえ。……それだったら <code>Sender</code> を <code>SyncSender</code> に変えて使う方が楽ですよね。</p>
</div>
<iframe src="https://mastodon.cardina1.red/@loliconductor/99954054794938883/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400"></iframe><script src="https://mastodon.cardina1.red/embed.js" async="async"></script>
<div class="paragraph">
<p><a href="https://github.com/crossbeam-rs/crossbeam-channel">crossbeam-channel</a>は、</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>Multi-producer multi-consumer channels for message passing</p>
</div>
<div class="paragraph">
<p>Channels are concurrent FIFO queues used for passing messages between threads.</p>
</div>
<div class="paragraph">
<p>Crossbeam’s channels are an alternative to the <a href="https://doc.rust-lang.org/std/sync/mpsc/index.html"><code>std::sync::mpsc</code></a> channels provided by the standard library.</p>
</div>
<div class="paragraph">
<p>(メッセージパッシング用のマルチプロデューサー・マルチコンシューマーチャネル)</p>
</div>
<div class="paragraph">
<p>(チャネルとはスレッド間メッセージパッシングのために使われる並行FIFOです。)</p>
</div>
<div class="paragraph">
<p>(クロスビームのチャネルは標準ライブラリーで提供されている<a href="https://doc.rust-lang.org/std/sync/mpsc/index.html"><code>std::sync::mpsc</code></a>のチャネルの代替となります。)</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p>だそうで、<a href="https://docs.rs/crossbeam-channel/0.1.2/crossbeam_channel/struct.Sender.html"><code>crossbeam_channel::Sender</code> のAPIリファレンス</a>見るとしっかり <code>Sync</code> も実装しててRocketの <code>State</code> に渡せるし、いいかも。</p>
</div>
<div class="paragraph">
<p>特に、今はRustの学習という目的が大きくて「とりあえず動くようにして、後でどんどん直していこう」というスタンスでやってるからライブラリーとかあんまり比較してないで今回も取り敢えず標準ライブラリーの <code>mpsc</code> を使ったけど、将来的には複数のワーカースレッドを作って、チャネルはワーカー間で共有したい、という気持ちになるだろうから、その時に<strong>マルチ</strong>コンシューマーというのは活きてくるはず( <code>mpsc</code> はコンシューマーの <code>Receiver</code> は <code>clone</code> もせずに一つだけ使う)。</p>
</div>
<div class="paragraph">
<p>情報ありがとうございます。</p>
</div>
<hr>
<div class="paragraph">
<p>余談ですけど、Rsutでググってると(最近は<a href="https://duckduckgo.com/">Duck Duck Go</a>を使うことも多いけど)、<a href="https://www.reddit.com/">Reddit</a>が引っかかるのが多くて驚いています。Redditってプログラミングについて議論するイメージなかった。</p>
</div>
https://diary.kitaitimakoto.net/2018/05/03.html
Re: Rocketでチャネルをうまく扱いたい
2018-05-03T00:00:00Z
2018-05-03T00:00:00Z
<div class="paragraph">
<p>細々とRustの勉強を続けています。なんか、一応解決したけど、これでいいのかなあ、っていう問題に遭遇したので、もっといい方法あるよっていう方はぜひ教えてください。</p>
</div>
<div class="paragraph">
<p><a href="https://rocket.rs/">Rocket</a>でウェブアプリケーションを動かしつつ、リクエスト処理が終わったら別スレッドのやつからウェブフックとか送りたい、と思って<a href="http://rust-lang-ja.org/rust-by-example/std_misc/channels.html">チャネル</a>を使ってみたのですが、はじめうまくいきませんでした。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="rust"><span class="c1">// ...</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">receiver</span><span class="p">):</span> <span class="p">(</span><span class="nn">mpsc</span><span class="p">::</span><span class="n">Sender</span><span class="o"><</span><span class="nb">String</span><span class="o">></span><span class="p">,</span> <span class="nn">mpsc</span><span class="p">::</span><span class="n">Receiver</span><span class="o"><</span><span class="nb">String</span><span class="o">></span><span class="p">)</span> <span class="o">=</span> <span class="nn">mpsc</span><span class="p">::</span><span class="nf">channel</span><span class="p">();</span>
<span class="k">let</span> <span class="n">web</span> <span class="nn">thread</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">move</span> <span class="p">||</span> <span class="p">{</span>
<span class="nn">rocket</span><span class="p">::</span><span class="nf">ignite</span><span class="p">()</span>
<span class="nf">.manage</span><span class="p">(</span><span class="n">sender</span><span class="p">)</span>
<span class="nf">.mount</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="nd">routes!</span><span class="p">[</span><span class="n">process_request</span><span class="p">])</span>
<span class="nf">.launch</span><span class="p">();</span>
<span class="p">});</span>
<span class="k">let</span> <span class="n">webhook</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">move</span> <span class="p">||</span> <span class="p">{</span>
<span class="nf">start_webhook</span><span class="p">(</span><span class="n">receiver</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">web</span><span class="nf">.join</span><span class="p">();</span>
<span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">webhook</span><span class="nf">.join</span><span class="p">();</span>
<span class="p">}</span>
<span class="nd">#[post(</span><span class="s">"/"</span><span class="nd">,</span> <span class="nd">data</span> <span class="nd">=</span> <span class="s">"<file>"</span><span class="nd">)]</span>
<span class="k">fn</span> <span class="nf">process_request</span><span class="p">(</span><span class="n">notifier</span><span class="p">:</span> <span class="n">State</span><span class="o"><</span><span class="nn">mpsc</span><span class="p">::</span><span class="n">Sender</span><span class="o"><</span><span class="nb">String</span><span class="o">>></span><span class="p">,</span> <span class="n">file</span><span class="p">:</span> <span class="n">Data</span><span class="p">)</span> <span class="k">-></span> <span class="nb">Result</span><span class="o"><></span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="c1">// ...</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>と、Rocketの<a href="https://rocket.rs/guide/state/">State</a>機能を使ってみようとしたところ、</p>
</div>
<div class="literalblock">
<div class="content">
<pre>error[E0277]: `std::sync::mpsc::Sender<std::string::String>` cannot be shared between threads safely</pre>
</div>
</div>
<div class="paragraph">
<p>と怒られる。 <code>State</code> はパラメーターに <code>Send</code> トレイトと <code>Sync</code> トレイトを要求して(<a href="https://api.rocket.rs/rocket/struct.State.html">Struct rocket::State</a>)、 <code>Sender</code> は <code>Sync</code> を実装しない(<a href="https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html">Struct std::sync::mpsc::Sender</a>)ので型から見るとそれは分かるんですけど、では何故そんな要求になってるの?</p>
</div>
<div class="paragraph">
<p>というのはRocketはマルチスレッドで動くから、スレッドをまたがって共有するために必要なことだったんでしょう。ということは分かるけど、公式ドキュメントの「<a href="https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/concurrency.html">並行性</a>」の章では <code>Sender</code> を <code>clone()</code> して各スレッドに渡すサンプルが載ってるので釈然としません。(まあ、今はまず動かして、後からどんどん直していこうというつもりで作ってるから先に進むけど。)</p>
</div>
<div class="paragraph">
<p>「<a href="https://matthias-endler.de/2017/rust-url-shortener/">Launching a URL Shortener in Rust using Rocket</a>」とか「<a href="https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html">Fearless Concurrency with Rust</a>」とか読んで、最終的に<a href="https://doc.rust-lang.org/std/sync/struct.Mutex.html">Mutex</a>を使って解決することにしましたが、あれ、今考えたら <code>Mutex<mpsc::Sender<String>></code> なんて作るくらいだったら<a href="https://doc.rust-lang.org/std/sync/mpsc/struct.SyncSender.html">SyncSender</a>を使えば同じなのでは? 帰ったらやってみよう。</p>
</div>
<div class="sect1">
<h2 id="_追記">追記</h2>
<div class="sectionbody">
<div class="paragraph">
<p><code>Mutex<mpsc::Sender<String>></code> を <code>mpsc::SyncSender<String></code> にしたらコンパイル通ったし問題なく動いた!</p>
</div>
</div>
</div>
https://diary.kitaitimakoto.net/2018/05/01.html
Rocketでチャネルをうまく扱いたい
2018-05-01T00:00:00Z
2018-05-01T00:00:00Z
<p><a href="http://www.rubydoc.info/gems/epub-parser/file/docs/Home.markdown">EPUB Parser</a>の<a href="http://www.rubydoc.info/gems/epub-parser/0.3.6/file/CHANGELOG.markdown#0_3_6">v0.3.6</a>をリリースした。</p>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">epub-parser (0.3.6): Parse EPUB 3 book loosely <a href="https://t.co/7UsY7J4lN1">https://t.co/7UsY7J4lN1</a></p>— RubyGems (@rubygems) <a href="https://twitter.com/rubygems/status/972485386424455168?ref_src=twsrc%5Etfw">2018年3月10日</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><a href="http://www.rubydoc.info/gems/epub-parser/0.3.6/file/CHANGELOG.markdown#0_3_4">v0.3.4</a>と<a href="http://www.rubydoc.info/gems/epub-parser/0.3.6/file/CHANGELOG.markdown#0_3_5">v0.3.5</a>で汚いコードがあったのだが、その部分がいつの間にかメソッドとして存在していた(<a href="http://www.rubydoc.info/gems/epub-parser/EPUB/Publication/Package/Manifest/Item#find_item_by_relative_iri-instance_method">Item#find_item_by_relative_iri</a>)のに気が付いたので、それを使うようにした。ついでにそのメソッドにバグが見付かったので修正した。このメソッド、少なくともこのライブライー内ではどこでも使われていなかったんだけど、なんで実装したんだろう……。</p>
<p>あと、このgemに付いてくる<a href="http://www.rubydoc.info/gems/epub-parser/file/docs/Epubinfo.markdown"><code>epubinfo</code></a>コマンドのドキュメントが不親切だったというか、追加されたオプションについて何も書いていなかったので追記した。人間用だけじゃなくて、YAMLやJSONフォーマットでも出力できます。昔は「みんなコマンドは<code>-h</code>オプション付きで一回くらい実行するよね?」という感じだった気がするなあ。</p>
<p>テスト中<a href="https://github.com/ko1/pretty_backtrace">PrettyBacktrace</a>を常に有効にしていたのも、デフォルト無効にして、必要な時だけ環境変数で有効にするようにした。これが有効だと体感で結構遅くなっちゃう程度にテストケースが多くなってきてるのと、たまにPrettyBacktraceのせいでSEGVするので(バグレポートはしてない。済みません、再現手順見付ける気力が……)。</p>
https://diary.kitaitimakoto.net/2018/03/10.html
EPUB Parser v0.3.6リリース
2018-03-10T00:00:00Z
2018-03-10T00:00:00Z
<p><a href="https://www.rust-lang.org/">Rust</a>勉強中です。</p>
<p>勉強中にありがちな、「分かってみればもう間違えないけど、最初は何が問題なのか全然分からない」というタイプのハマり方をしたので記録として書きます。</p>
<p>EPUBファイルの中身を調べようと思って、<a href="https://crates.io/crates/zip">zipクレート</a>(ライブラリー)を使った次のようなコードを書いていました(EPUBファイルは拡張子を変えたZIPアーカイブなのです)。</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">extern</span> <span class="k">crate</span> <span class="n">zip</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="n">fs</span><span class="p">;</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">path</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">env</span><span class="p">::</span><span class="nf">args</span><span class="p">()</span><span class="nf">.nth</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">let</span> <span class="n">file</span> <span class="o">=</span> <span class="nn">fs</span><span class="p">::</span><span class="nn">File</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="o">&</span><span class="n">path</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">archive</span> <span class="o">=</span> <span class="nn">zip</span><span class="p">::</span><span class="nn">ZipArchive</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">file</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">archive</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">f</span> <span class="o">=</span> <span class="n">archive</span><span class="nf">.by_index</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">let</span> <span class="n">name</span> <span class="o">=</span> <span class="n">f</span><span class="nf">.name</span><span class="p">()</span><span class="nf">.to_string</span><span class="p">();</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">name</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">let</span> <span class="n">f</span> <span class="o">=</span> <span class="n">archive</span><span class="nf">.by_name</span><span class="p">(</span><span class="s">"META-INF/container.xml"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">f</span><span class="nf">.bytes</span><span class="p">()</span><span class="nf">.next</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>
<p>で、実行してみると、ファイル一覧は表示できる(<code>println!("{}", name)</code>)のだけど、ファイルの中身を読み取る(<code>f.bytes().next().unwrap()</code>)ところでエラー。</p>
<pre><code>% cargo run api-design.epub
Compiling handle-epub v0.1.0 (file:///Users/ikeda/src/gitlab.com/KitaitiMakoto/learning-rust/handle-epub)
error[E0599]: no method named `bytes` found for type `zip::read::ZipFile<'_>` in the current scope
--> src/main.rs:18:24
|
18 | println!("{:?}", f.bytes().next().unwrap());
| ^^^^^
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope, perhaps add a `use` for it:
|
3 | use std::io::Read;
|
error: aborting due to previous error
error: Could not compile `handle-epub`.
</code></pre>
<p><code>zip::read::ZipFile<'_></code>に<code>bytes</code>メソッドが無いと言われて、そんなばかなと思いつつ<a href="http://mvdnes.github.io/rust-docs/zip-rs/zip/read/struct.ZipFile.html">zip::read::ZipFile構造体のAPIリファレンス</a>を見ると</p>
<blockquote>
<p><code>impl<'a> Read for ZipFile<'a></code><br />
…<br />
<code>fn bytes(self) -> Bytes<Self></code><br />
Transforms this Read instance to an [Iterator] over its bytes. Read more</p>
</blockquote>
<p>やっぱりある。</p>
<p>「<code>ZipFile</code>はちゃんと<code>Read</code>トレイトを実装しているのになーなんでだろうなー」と一日くらい悩んだのだけど、解決は一瞬でした。この悩み方に既にヒントが現れていて、悩んでいるうちに『<a href="https://rust-lang-ja.github.io/the-rust-programming-language-ja/">プログラミング言語Rust</a>』の次の文を思い出したのです。</p>
<blockquote>
<p>第1に、あなたのスコープ内で定義されていないトレイトは適用されません。例えば、標準ライブラリは <code>File</code> にI/O機能を追加するための <code>Write</code> トレイトを提供しています。デフォルトでは、 <code>File</code> は <code>Write</code> で定義されるメソッド群を持っていません。<br />
(略)<br />
始めに <code>Write</code> トレイトを <code>use</code> する必要があります。</p>
</blockquote>
<p>(「<a href="https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/traits.html">トレイト</a>」より)</p>
<p>というわけで答えは簡単、</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Read</span><span class="p">;</span>
</code></pre></div></div>
<p>の一行を足すことで、あっさりとコンパイルが通ってコードも実行できたのでした。</p>
<pre><code>% cargo run api-design.epub
Compiling handle-epub v0.1.0 (file:///Users/ikeda/src/gitlab.com/KitaitiMakoto/learning-rust/handle-epub)
Finished dev [unoptimized + debuginfo] target(s) in 0.90 secs
Running `target/debug/handle-epub api-design.epub`
mimetype
META-INF/
META-INF/com.apple.ibooks.display-options.xml
META-INF/container.xml
OEBPS/
OEBPS/content.opf
OEBPS/images/
OEBPS/images/backslash.jpg
OEBPS/images/cover.jpg
OEBPS/images/p-001-000fig.jpg
OEBPS/images/p-001-001-1fig.jpg
OEBPS/images/p-001-001-2fig.jpg
...
OEBPS/text/p-005-003.xhtml
OEBPS/text/p-005-004.xhtml
OEBPS/text/p-005-005.xhtml
OEBPS/text/p-005-006.xhtml
OEBPS/text/p-006-001.xhtml
OEBPS/text/p-007-001.xhtml
OEBPS/text/p-008-001.xhtml
OEBPS/text/p-008-002.xhtml
Ok(60)
</code></pre>
https://diary.kitaitimakoto.net/2018/02/20.html
実装されているはずのメソッドが呼べなくてハマった
2018-02-20T00:00:00Z
2018-02-20T00:00:00Z
<p><a href="http://www.hyuki.com/mm/">結城メルマガ</a>の『<a href="https://mm.hyuki.net/n/n9a945789ad1a">「ぐっとがんばる」話。</a>』がよかった。</p>
<blockquote>
<p>ある日、Evernoteのノート整理をしていました。
いいアイディアや書籍の案はいろいろあるんだけど、
断片ばかりではだめですね。
どこかで「ぐっとがんばる」ことをしないと、
断片をいくら集めてもまとまりません。</p>
</blockquote>
<p><a href="https://scrapbox.io/">Scrapbox</a>を使い始めて、ちょっとしたことをどんどんメモしていって、リンクが繋がる気持ちよさに悦に入ったりしているのだけど、やっぱりそれだけでは何にもならない。メモしたから安心、と、脳を解放して次のこと(やってた仕事に戻るとか、ツイッターアプリ開くとか、最近だとツイッターよりMastodon)に行ってしまうのではなく、そこでぐっとこらえて、メモを眺め直して、スマホから離れて、考えてみる。すると、ただの思い付きよりも先に進むことができる。</p>
<p>というのを実感した最近だったから、結城さんのこの話が刺さった。</p>
<p>この、「誰か他人(著名人)が自分が感じたのと同じことを言っている」ということの嬉しさは何だろうな。自分が肯定されたように感じる、というのもあるだろうけど、「考えを進めるための足場が出来た」という気持ちよさが大きい気がする。</p>
<p>余談だけど、Scrapboxに音声入力でメモを取った時が、考えが進みやすい気がする。体を使ったほうがいいということか。とは言え会話がいいのかというとそう簡単でもない。大体、会話しているようなスピードで僕は物を考えられないし、会話しながらだとそっちにエネルギーが奪われてしまう。一人がやっぱりいい。</p>
https://diary.kitaitimakoto.net/2017/10/17.html
「ぐっとがんばる」話。
2017-10-17T00:00:00Z
2017-10-17T00:00:00Z
<p>Rubyのライブラリーは、RubyGemとして配布されます。自分でこのRubyGemを作るにあたり、いつも必要になるファイルを作成するのに、多くの人は<a href="https://bundler.io/">Bundler</a>というgemの<code>bundle gem</code>コマンドを使っていると思います。
Bundlerもいいのですが、最近(ここ三年くらい)僕は<a href="https://github.com/ruby-ore/ore">Ore</a>という別のgemを使っているので、紹介してみたいと思います。</p>
<p>Oreを使うには</p>
<pre><code>% gem install ore
</code></pre>
<p>します。ツールの性質上、Gemfileに書いたりすることはなく、グローバル(やchruby、rbenvなどの環境下)にインストールします。すると<code>mine</code>コマンドが使えるようになるので、これでgemのひな形を作ります。</p>
<pre><code>% mine --bsd \
--homepage=https://gitlab.com/KitaitiMakoto/pathname-temporary \
--rubygems-tasks \
--test-unit \
--yard \
pathname-temporary
Generating /Users/ikeda/src/gitlab.com/KitaitiMakoto/pathname-temporary
create lib
create lib/pathname/temporary
create test
create .document
create .yardopts
create test/helper.rb
create test/test_pathname-temporary.rb
create LICENSE.txt
create Gemfile
create .gitignore
create ChangeLog.md
create README.md
create Rakefile
create pathname-temporary.gemspec
create lib/pathname/temporary/version.rb
create lib/pathname/temporary.rb
run git init from "."
</code></pre>
<p>すごいたくさんオプションを付けているけど、必須な部分だけだと次の通り。</p>
<pre><code>% mine pathname-temporary
</code></pre>
<p>ここでのオプションは次のような意味になります。</p>
<dl>
<dt><code>--bsd</code></dt>
<dd>二条項BSDライセンスにする。gemspecファイルのライセンスの所が<code>"BSD"</code>となり、具体的なライセンス内容が<code>LISENCE.txt</code>ファイルに書かれるようになる。</dd>
<dt><code>--homepage=...</code></dt>
<dd>ホームページのURI。gemspecファイルの<code>homepage</code>の所がここで指定したURIになるほか、<code>README.md</code>にも使われる。</dd>
<dt><code>--rubygems-tasks</code></dt>
<dd><code>rake build</code>とか<code>rake release</code>とか、gemパッケージを扱うRakeタスク用に、<a href="https://github.com/postmodern/rubygems-tasks">rubygems-tasks</a> gemを使う。gemspecファイルの依存gemにrubygems-taskが追加され、<code>Rakefile</code>内でそれが使われる。<code>--no-rubygems-tasks</code>にすると、<code>Rakefile</code>では<code>bundler/gem_tasks</code>が読み込まれて、Bundlerが提供する各タスクが定義される。</dd>
<dt><code>--test-unit</code></dt>
<dd>テスティングフレームワークに<a href="https://test-unit.github.io/">test-unit</a>を使う。<code>Rakefile</code>でtest-unit用のタスクが定義され、<code>test</code>ディレクトリーとヘルパーファイルが作成される。これを指定しない場合は、デフォルトで<a href="https://rspec.info/">RSpec</a>が使われる。</dd>
<dt><code>--yard</code></dt>
<dd>ドキュメンテーションツールとして<a href="https://yardoc.org/">YARD</a>を使う。依存gemに(略)、<code>Rakefile</code>で(略)、<code>.yardopts</code>ファイルも作成される。これを指定しない場合、<a href="https://docs.ruby-lang.org/ja/latest/library/rdoc.html">RDoc</a>が使われる。</dd>
</dl>
<p>他にも色々あるので、<code>--help</code>オプションで見てみてください。</p>
<pre><code>% mine --help
Usage:
mine PATH
Options:
[--markup=markdown|textile|rdoc]
# Default: markdown
[--markdown], [--no-markdown]
[--textile], [--no-textile]
-T, [--templates=TEMPLATE [...]]
-n, [--name=NAME]
-n, [--name=NAME]
-N, [--namespace=NAMESPACE]
-V, [--version=VERSION]
# Default: 0.1.0
-s, [--summary=SUMMARY]
# Default: TODO: Summary
-D, [--description=DESCRIPTION]
# Default: TODO: Description
-A, [--author=NAME]
-a, [--authors=NAME [...]]
-e, [--email=EMAIL]
-U, --website, [--homepage=HOMEPAGE]
-B, [--bug-tracker=BUG_TRACKER]
Template options:
[--apache], [--no-apache]
[--bin], [--no-bin]
[--bsd], [--no-bsd]
[--bundler], [--no-bundler]
# Default: true
[--code-climate], [--no-code-climate]
[--gem-package-task], [--no-gem-package-task]
[--gemspec-yml], [--no-gemspec-yml]
[--git], [--no-git]
# Default: true
[--gpl], [--no-gpl]
[--hg], [--no-hg]
[--lgpl], [--no-lgpl]
[--minitest], [--no-minitest]
[--mit], [--no-mit]
# Default: true
[--rdoc], [--no-rdoc]
# Default: true
[--rspec], [--no-rspec]
# Default: true
[--rubygems-tasks], [--no-rubygems-tasks]
# Default: true
[--test-unit], [--no-test-unit]
[--travis], [--no-travis]
[--yard], [--no-yard]
</code></pre>
<p>gemspecファイルを、RubyじゃなくてYAMLファイルで管理する<code>--gemspec-yml</code>オプションとか、面白いですね。</p>
<p>更に、(<code>mine</code>ではなくて)<code>ore</code>コマンドで各種テンプレートをインストールすることで、このオプションを増やすこともできます。</p>
<pre><code>% ore install git://github.com/ruby-ore/rbenv.git
</code></pre>
<p>を実行すると、<code>mine</code>コマンドで<code>--rbenv</code>オプションが使えるようになります。</p>
<p>毎回このオプションを指定るするのは覚えられないので、<code>~/.ore/options.yml</code>に、毎回使うオプションを指定して置くことができます。著者名やメールアドレスなんかもデフォルトを指定しておけます。</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">gemspec_yml</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">rubygems_tasks</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">rspec</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">yard</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">markdown</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">authors</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">Alice</span>
<span class="na">email</span><span class="pi">:</span> <span class="s">alice@example.com</span>
</code></pre></div></div>
https://diary.kitaitimakoto.net/2017/10/16.html
Oreでgemのひな形を作る
2017-10-16T00:00:00Z
2017-10-16T00:00:00Z
<p><a href="https://note.mu/">note</a>や<a href="https://cakes.mu/">cakes</a>をやってる<a href="https://www.pieceofcake.co.jp/">ピースオブケイク</a>さんと勤め先の<a href="https://corkagency.com/">コルク</a>とで、毎週月曜ジャンプの発売日(僕の住んでいた札幌では火曜発売だったけどね)に<a href="https://note.mu/hashtag/%E3%83%86%E3%83%83%E3%82%AF%E4%BC%9A%E8%AD%B0?f=new&search=on">#テック会議</a>と称してみんなでテック記事を上げるようにしましょうってなって、面白そうだし<a href="https://note.mu/">note</a>でなくて自分の日記でもいいってことだったので参加してみます。<a href="https://note.mu/hashtag/%E3%83%86%E3%83%83%E3%82%AF%E4%BC%9A%E8%AD%B0?f=new&search=on">これまでの記事</a>を見てみると抽象的な話が多いようで、普段書いてる「これやってみた」という記事とは違うけど頑張ります。</p>
<p>別に参加者を絞るつもりはないようなので、皆さんも興味があれば更新日を月曜にずらしてみてください。<a href="https://note.mu/">note</a>を使う場合は<a href="https://note.mu/hashtag/%E3%83%86%E3%83%83%E3%82%AF%E4%BC%9A%E8%AD%B0?f=new&search=on">#テック会議</a>でタグ付けしておくと探しやすくてありがたいです。特に報酬とかはありません。</p>
<p>さて長い前置きだったけど今日は、この前<a href="https://hypothes.is/">Hypothes.is</a>が出してきた、とても興奮するニュースの話をします。</p>
<p><a href="https://hypothes.is/blog/ebook-partnership/">A partnership to bring open annotation to eBooks</a><br />
(電子書籍にオープンなアノテーションをもたらすパートナーシップ)</p>
<p>一行で言うと、「ウェブページやPDFへのアノテーション用のウェブアプリケーション開発及びそのホスティングサービス運用を行ってきたHypothes.isが、EPUBにアノテーションを付けられるようにするべく、複数の組織とパートナーシップを結ぶ」というニュースです。これで伝わる人は殆どいないと思うので解説します。</p>
<h2 id="heading-2017-03-13-table-of-contents">目次</h2>
<ol>
<li><a href="#heading-2017-03-13-whats-annotation">アノテーションとは</a></li>
<li><a href="#heading-2017-03-13-whats-hypothesis">アノテーションサービスのHypothes.is</a></li>
<li><a href="#heading-2017-03-13-epub-ebook-format">電子書籍フォーマットのEPUB</a></li>
<li><a href="#heading-2017-03-13-hypothesis-partnership">Hypothes.isのパートナーシップ</a></li>
</ol>
<h2 id="heading-2017-03-13-whats-annotation">アノテーションとは</h2>
<p>アノテーションというのは日本語だと「注釈」です。例えばウェブページで、ある文章に線を引っ張って自分のメモ書きを残すことはアノテーションです。</p>
<p>「注釈」という言葉からは外れると感じますが、単に線を引っ張るだけでも、ここではアノテーションと呼びます。</p>
<figure>
<a href="https://gyazo.com/1ff7a03324fdc08da7f2f86c245431db"><img src="https://gyazo.com/1ff7a03324fdc08da7f2f86c245431db.png" alt="" /></a>
<figcaption>先のニュースページでハイライトされている様子。ハイライトされた部分の背景が黄色になっている。</figcaption>
</figure>
<p>また、ページの一部でなく、ページ全体に対して何か言及することもアノテーションです。<a href="http://b.hatena.ne.jp/">はてなブックマーク</a>なんかがいい例だと思います。</p>
<figure>
<a href="https://gyazo.com/47eb628941ce4e66cbc7928bb254753c"><img src="https://gyazo.com/47eb628941ce4e66cbc7928bb254753c.png" alt="" /></a>
<figcaption>ページ全体に対するコメントを残すのもアノテーション。画像ははてなブックマークの例。</figcaption>
</figure>
<p>先と同じパターンで、コメントのないブックマークも、アノテーションです。</p>
<h2 id="heading-2017-03-13-whats-hypothesis">アノテーションサービスのHypothes.is</h2>
<p><a href="https://hypothes.is/">Hypothes.is</a>は、こういうアノテーションを、ウェブページとPDFに付けられるようにするウェブアプリケーションです。ページをハイライトしたりコメントを入力したりするためのChrome拡張やJavaScriptウィジェットを作ったり、そのアノテーションを保存・参照するためのサーバー用のアプリケーションを開発しています。</p>
<p>と同時に、そのアプリケーションを実際に運用して、無償で提供してもいます。この日記にもJavaScriptウィジェットを埋め込んでいて、記事を(一覧ページでなく)個別ページで読んでいる場合には右側にそのためのバーが見えているはずです。</p>
<figure>
<a href="https://gyazo.com/777f1accfa73f2787a6b4c1bb0e0cd72"><img src="https://gyazo.com/777f1accfa73f2787a6b4c1bb0e0cd72.png" alt="" /></a>
<figcaption>アノテーションサービスHypothes.isのためのウィジェットを、無償で自分のウェブサイトにも埋め込める。</figcaption>
</figure>
<p>サイトの運用者はこのウィジェットを埋め込むことで、ページ全体または一部に閲覧者がハイライトやコメントを残せるようにできます。サイト側が対応していなくても、Chrome拡張を入れることで閲覧者はどのページにもアノテーションを付けられるようになります。付けられたアノテーションは(許可すれば)誰でも見ることができます。</p>
<p>こうして付けたアノテーションはHypothes.isのサーバーに保管され、どの端末、どのブラウザーでも見られるようになります(ブラウザー拡張は今のところChromeだけですが。Firefoxのは開発中で、自分でビルドして入れることはできます)。</p>
<h2 id="heading-2017-03-13-epub-ebook-format">電子書籍フォーマットのEPUB</h2>
<p>電子書籍では、こうした「アノテーションを付けて、それをどの端末でも参照できる」という体験を既に経験していると思います。Kindleのことです。</p>
<figure>
<a href="https://gyazo.com/f29495ff624f3e64f395cb21f9247848"><img src="https://gyazo.com/f29495ff624f3e64f395cb21f9247848.png" alt="" /></a>
<figcaption>Kindleでは電子書籍にハイライトやコメント、即ちアノテーションを付けることができる。</figcaption>
</figure>
<p>ところで、EPUBという電子書籍フォーマットがあります。IDPFという団体が策定した<strong>オープン</strong>なフォーマットです(KindleのはKindle Formatとかmobiとか呼ばれて、Kindleを作っているAmazon社が仕様を決めて運用しています。つまりオープンではありません)。仕様は誰でも見ることができますし、従って誰でも閲覧や作成用のアプリケーションを作れます。iBooksなどで読むことができます。</p>
<p>ちなみにPDFやWordファイル(*.docx)、Excelファイル(*.xlsx)、MP3なんかもオープンなファイルフォーマットです。Photoshop用ファイル(*.psd)やInDesign用ファイル(*.indd)はオープンではありません。</p>
<h2 id="heading-2017-03-13-hypothesis-partnership">Hypothes.isのパートナーシップ</h2>
<p>今回のニュースは、Hypothes.isがこのEPUBにも対応するべく、幾つかの組織とパートナーシップを結ぶ、という物です。Hypothes.isとのパートナーシップが発表された組織は以下の五つ。</p>
<dl>
<dt><a href="http://dlib.nyu.edu/dlts/">NYU Libraries</a></dt>
<dd>ニューヨーク大学の、デジタルな物を処理し、アクセスを可能にし、また保管する所。</dd>
<dt><a href="https://nyupress.org/">NYU Press</a></dt>
<dd>ニューヨーク大学の出版社?</dd>
<dt><a href="http://www.evidentpoint.com/">Evident Point</a></dt>
<dd>電子出版ソリューションを提供する提供する会社。Readium(下記参照)のコアコントリビューターを数人抱えているらしい。</dd>
<dt><a href="http://readium.org/">Readium Foundation</a></dt>
<dd>ReadiumJSという、EPUBを扱うJavaScriptのリファレンス実装を作っている所。ReadiumJSはDRMも扱えるとのこと。</dd>
<dt><a href="http://futurepress.org/">EPUBjs project</a></dt>
<dd>epub.jsという、Readiumとはまた別のJavaScript実装を作っているプロジェクト。既にHypothes.isと連携したプルーフオブコンセプトを作った実績がある。</dd>
</dl>
<p>イデオロギー的に僕はオープンな物やフリーな物を支持しているので、オープンなファイルフォーマットにオープンなアプリケーションでアノテーションが付けられるというこのニュースにはとても興奮しました。</p>
<p>いきなりイデオロギーの話が出てきて面喰らうかも知れませんが、フリーとかオープンとかは殆どイデオロギーの話だと思っています<sup id="fnref:free-open-as-ideology" role="doc-noteref"><a href="#fn:free-open-as-ideology" class="footnote" rel="footnote">1</a></sup>。</p>
<p>ま、イデオロギーは置いておいても、オープンであればロックインされない(Kindleだと、Kindleがなくなると同時に自分の本のコメントが失われてしまう)、とか無料だとかメリットがあります。例えばiBooksで付けたブックマークをGoogle Play Booksで開くといったこともできるかも知れません(Appleがブックマークデータをダウンロードさせてくれれば)。</p>
<p>というような感じでいいのか知ら? 皆さんも、<a href="https://note.mu/hashtag/%E3%83%86%E3%83%83%E3%82%AF%E4%BC%9A%E8%AD%B0?f=new&search=on">#テック会議</a>ぜひ参加してみてください。</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:free-open-as-ideology" role="doc-endnote">
<p>川上量生『<a href="https://www.iwanami.co.jp/book/b226338.html">鈴木さんにも分かるネットの未来</a>』でそんな感じのことが書かれていてはっとしました。 <a href="#fnref:free-open-as-ideology" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>
https://diary.kitaitimakoto.net/2017/03/13.html
Hypothes.isがEPUBへのアノテーションのためのパートナーシップを結ぶ
2017-03-13T00:00:00Z
2017-03-13T00:00:00Z
<p>この日記は<a href="https://www.polymer-project.org/1.0/">Polymer</a>で作っている、つまり<a href="http://webcomponents.org/">Webコンポーネント</a>を使っている。そのために表示が遅い。表示するまでに</p>
<ol>
<li>Webコンポーネントに必要なpolyfillを読み込む</li>
<li>Polymerライブラリーを読み込む</li>
<li>各種カスタムエレメント定義をロードする</li>
<li>JavaScriptで各種カスタムエレメント定義を実行する</li>
<li>HTML中の各種カスタムエレメントを有効化する</li>
</ol>
<p>というステップがあって、これを毎ページ繰り返すからだ。前々から何とかしたいなあとは思っていて、この連休で、サイト内リンクをpjaxにすることで少し改善させた。</p>
<p>各ステップはpjaxによって以下のように改善される。</p>
<h2 id="heading-2017-01-10-table-of-contents">目次</h2>
<ol>
<li><a href="#heading-2017-01-10-whats-pjax">pjaxとは</a></li>
<li><a href="#heading-2017-01-10-loading-polyfill">polyfill読み込み</a></li>
<li><a href="#heading-2017-01-10-loading-polymer">Polymerライブラリーの読み込み</a></li>
<li><a href="#heading-2017-01-10-loading-custom-elements">カスタムエレメント読み込み</a></li>
<li><a href="#heading-2017-01-10-defining-custom-elements">カスタムエレメント定義の実行</a></li>
<li><a href="#heading-2017-01-10-pjax-using-polymer">Polymerでpjax</a>
<ol>
<li><a href="#heading-2017-01-10-app-location">app-location</a></li>
<li><a href="#heading-2017-01-10-iron-ajax">iron-ajax</a></li>
</ol>
</li>
<li><a href="#heading-2017-01-10-afterwords">終わりに</a></li>
</ol>
<h2 id="heading-2017-01-10-whats-pjax">pjaxとは</h2>
<p>有名なので不要だとは思うけど、一応pjaxを説明しておく。</p>
<p>pjaxは、</p>
<ul>
<li>Ajaxによる画面遷移</li>
<li><code>location</code>オブジェクト(アドレスバーのURL)の書き換え</li>
</ul>
<p>の組み合わせだ。サイト内の別ページへのリンクをタップした際に、通常のブラウザーの画面遷移をする代わりに、JavaScriptでリンク先のHTMLを取得して、現在のページと置き換える。今回は、<code>title</code>要素と<code>main</code>要素を置き換えることで、画面遷移としている。ページ全体でなく、一部の書き換え・更新にもよく使われる。</p>
<p>同時に<code>location</code>を書き換えることで、ブラウザーの進む/戻る・リロード、アドレスバーからURLをコピーしての共有など、通常の画面遷移であればできていることを可能にしている。</p>
<p>後者のためにJavaScriptの<code>pushState</code>機能を使っていることからpjaxと名付けられている:<br />
<a href="https://github.com/defunkt/jquery-pjax">defunkt/jquery-pjax: pushState + ajax = pjax</a></p>
<p>pjaxは「現在のDOMツリー内での置き換え」が機能なので、外から飛んできて最初に表示するページでは役に立たない。</p>
<h2 id="heading-2017-01-10-loading-polyfill">polyfill読み込み</h2>
<p>Webコンポーネントはまだ策定中・ブラウザー実装途中の仕様なので、クロスブラウザーでは動かない。具体的には、Chromeでしか全部は動かない。そこで、他のブラウザーでも動くよう、polyfillやshimを読み込む必要がある。</p>
<p>これにはWebコンポーネント仕様の一部であるカスタムエレメントなど以外にも、URLコンストラクターやPromiseなどのpolyfillも含まれる。サーバー側でブラウザーの判定などはしていないので(GitHub Pagesなのでそもそもできない)、Chromeのように不要であっても読み込んでいる。</p>
<p>こういうのは普通、<code>libs.js</code>のような一つのファイルにまとめることでリクエスト回数を減らすものだけど、面倒くさくて後回しにしている(後回しにするうちにGitHub PagesでHTTP/2が使えるようになるといいなあ、という期待もちょっとある)。</p>
<p>pjaxによって、<code>main</code>の外にある<code>script</code>の読み込みと実行がスキップされるので、パフォーマンスがよくなっている。あと、そもそも同じ<code>script</code>を二回読み込んだりすると、イベントリスナーの登録が複数回行われたりして意図しない動作になりがちなので、基本的に<code>script</code>はpjaxでの置き換え対象に入れたくない。</p>
<h2 id="heading-2017-01-10-loading-polymer">Polymerライブラリーの読み込み</h2>
<p>ページをPolymerで作っている以上、当然Polymerを読み込む必要がある。</p>
<p>これもJavaScriptの読み込みなので、上と同じくpjaxによってスキップし、パフォーマンスを向上させている。</p>
<h2 id="heading-2017-01-10-loading-custom-elements">カスタムエレメント読み込み</h2>
<p>Polymerが提供していてマテリアルデザインを実現するのに便利な<a href="https://elements.polymer-project.org/browse?package=paper-elements">Paper Elements</a>や自作の物など、各種カスタムエレメントは通常一つのHTMLファイルになっている。その中に、HTMLタグの他CSS宣言や要素定義のJavaScriptを書くようになっているし、僕もそうしている。一つのカスタムエレメントが複数のカスタムエレメントの組み合わせであることもよくあって、依存エレメントの分HTMLを読み込む必要があるのが普通だ。</p>
<p>先のJavaScriptライブラリーとは違って、これはさすがにリクエストが多くなり過ぎるので<a href="https://github.com/Polymer/vulcanize">vulcanize</a>によって一つのファイルにまとめている。その一つにまとめたHTMLファイルは<code>head</code>要素中の<code>link</code>要素</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><link</span> <span class="na">href=</span><span class="s">"components/elements.vulcanized.html"</span> <span class="na">rel=</span><span class="s">"import"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>によって読み込んでいる(実際にはこのタグを書き出すヘルパーが<a href="https://github.com/KitaitiMakoto/middleman-web_components">Middleman Web Components</a>にあるので、それを使っている)。</p>
<p>これも<code>main</code>要素の外にあるので、pjaxによってスキップしている。</p>
<h2 id="heading-2017-01-10-defining-custom-elements">カスタムエレメント定義の実行</h2>
<p>カスタムエレメントは、単にHTML中に<code><paper-card></code>などタグを書くだけでは有効にならない(知らないタグとして扱われる)。このタグがカスタムエレメントの物であることをブラウザーに知らせ、各種機能を定義するにはJavaScriptを使う必要がある(参考:<a href="https://developers.google.com/web/fundamentals/getting-started/primers/customelements">Custom Elements v1: Reusable Web Components</a>)。</p>
<p>この定義は、上の<code>elements.vulcanized.html</code>に書かれているので、pjaxによってやはりこのステップもスキップできる。</p>
<p>これまでのステップは、(HTTPヘッダーやService Workerなどで)キャッシュを上手に使うことでも飛ばせるのだけど、カスタムエレメント定義は、ファイルを読み込んだ<strong>後</strong>の処理なので、ページ遷移ごとに毎回実行する必要があり、キャッシュできない。なのでここが、キャッシュ機構を入れてもなおpjaxが活きるところだと思う。</p>
<p>本当はpolyfillやPolymer読み込みをキャッシュしても、同様にJavaScript実行はページ表示毎に発生するのだけど、カスタムエレメント定義は特に多くなりがちなので特別に節を設けた。</p>
<h2 id="heading-2017-01-10-activating-custom-elements">カスタムエレメントの有効化</h2>
<p>カスタムエレメント定義が終わったらブラウザーは、HTML中のカスタムエレメントタグ(に相当するDOMノード)を、そのカスタムエレメントとして扱い始める。このステップはpjaxではスキップできない。</p>
<h2 id="heading-2017-01-10-pjax-using-polymer">Polymerでpjax</h2>
<p>ようやく本題だけど、今回のpjax実装では、Polymerが提供している<a href="https://elements.polymer-project.org/elements/app-route?active=app-location">app-location</a>と<a href="https://elements.polymer-project.org/elements/iron-ajax">iron-ajax</a>というカスタムエレメントを使って実現してみた(<a href="https://github.com/KitaitiMakoto/apehuci/blob/26e1f03ac1a28983faa094b32b98ed1ad4822c26/source/components/blog-router.html">blog-router.html</a>)。pjaxは普通、全部JavaScriptでやるものだと思うけど、半分くらいの処理はHTMLタグを書くことで実現できてしまっていて、不思議な感じがした。</p>
<h3 id="heading-2017-01-10-app-location">app-location</h3>
<p><code>app-location</code>を使うと、サイト内リンクが全部無効化される。その代わり、イベントリスナーでクリックイベントをハンドリングしたり、リンクに関する情報をデータバインディングを使って別の要素に渡したりできる。</p>
<p>今回は</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><app-location</span> <span class="na">url-space-regex</span><span class="err">$="[[</span><span class="na">baseRegex</span><span class="err">]]"</span> <span class="na">route=</span><span class="s">"{{route}}"</span> <span class="na">id=</span><span class="s">location</span><span class="nt">></app-location></span>
</code></pre></div></div>
<p>と<code>route</code>というオブジェクトに、画面遷移に関する情報を入れることにした。</p>
<p><code>{{...}}</code>はPolymerの提供するデータバインディング用の記法で、他に<code>{{route}}</code>と書かれた場所と連動する(<a href="https://www.polymer-project.org/1.0/docs/devguide/data-binding">Data binding - Polymer Project</a>)。</p>
<h3 id="heading-2017-01-10-iron-ajax">iron-ajax</h3>
<p><code>app-location</code>は、アドレスバーのURLの書き換えはしてくれるけど、実際のリクエストは投げてくれない。ので、それをJavaScriptでイベントハンドラーとして書くか、今回のように別の要素と連携させないと意味がない。</p>
<p><code>iron-ajax</code>はその名の通りAjaxしてくれるカスタムエレメントで、画面上にレンダリングはされない。純粋にJavaScript的な実行のためにある。これが要素になっているが不思議な感じがする。</p>
<p>これもデータバインディングの記法を使いつつ</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><iron-ajax</span> <span class="na">url=</span><span class="s">"{{route.path}}"</span> <span class="na">handle-as=</span><span class="s">document</span> <span class="na">auto</span> <span class="na">on-response=</span><span class="s">transit</span> <span class="na">on-error=</span><span class="s">fallback</span> <span class="na">id=</span><span class="s">ajax</span><span class="nt">></iron-ajax></span>
</code></pre></div></div>
<p>と書くことで、<code>app-location</code>の<code>route</code>オブジェクトから<code>path</code>プロパティを取り出してセットしている(<code>path</code>の他に<code>hash</code>プロパティもあって、本当はこれもちゃんとハンドリングしないといけない)。</p>
<p><code>auto</code>属性をつけていると<code>url</code>属性が変わった際に自動でAjaxが行われるので、</p>
<pre><code>リンクをタップ -> app-locationのroute属性変更 -> iron-ajaxのurl変更 -> Ajaxリクエスト
</code></pre>
<p>という流れをJavaScriptを書かずに実現してくれる(リクエストを間引くのも、使ってないけど、HTML属性によって定義できる)。</p>
<p>あとはレスポンス時やエラー時の処理をそれぞれ<code>transit</code>、<code>fallback</code>という関数としてJavaScriptで書いてやって出来上がりだ。<code>transit</code>としてJavaScriptで書いた処理は、殆ど<code>title</code>要素と<code>main</code>要素の書き換えのみ。</p>
<h2 id="heading-2017-01-10-afterwords">終わりに</h2>
<p>Polymerによるpjaxはこのようにして実現できる。これには、公式サイトの以下のページがとても参考になった。</p>
<p>» <a href="https://www.polymer-project.org/1.0/toolbox/routing">Routing with <app-route> - Polymer Project</a></p>
<p>余談だけど、<a href="https://github.com/turbolinks/turbolinks">Turbolinks</a>を使うと自分で実装しなくてよかったのかも知れないなと思っている。</p>
<p>あと、今回、ページ内に一つ<code>blog-router</code>を置くことによって、つまり一元的なルーターを使ってpjaxを実現している。ReactやAngularでもルーターライブラリーがあるように、この手の処理は一元的なルーターでやるのが普通なのかも知れない。でも、次へリンクなどのHTML要素に結び付く形で、それがpjaxによる遷移かどうかを管理できるようにする、引いては、そのリンクのカスタムエレメントの機能としてpjax処理を実装できた方が、コンポーネント志向としてはいいのかも知れないなあと、やった後で思った。気が向いたらやってみるかも(そして、世界中のみんながルーターを一元的に作っている理由を知るのだ、きっと)。</p>
<p>今この記事書いてて気付いたけど、ページ内リンクが機能しなくなってしまった……。もう遅いので、後日の対応とします。(追記。直しました。)</p>
https://diary.kitaitimakoto.net/2017/01/10.html
Polymerでpjaxする、またはapp-locationの使い方
2017-01-10T00:00:00Z
2017-01-10T00:00:00Z
<p><a href="http://www.rubydoc.info/gems/epub-parser/file/docs/Home.markdown">EPUB Parser</a> gemのリポジトリーを、<a href="https://github.com/">GitHub</a>から<a href="https://gitlab.com/">GitLab</a>に移してみた:<br />
<a href="https://gitlab.com/KitaitiMakoto/epub-parser">https://gitlab.com/KitaitiMakoto/epub-parser</a></p>
<p>ここのところフリーとかオープンソースソフトウェアとサービスとかについて感じるところの記事を書いていて(未公開)、その過程でGitLabのことを思い出したので、やってみたのだった。</p>
<p>前にクローンはしていたのだけど、<code>master</code>ブランチへのフォースプッシュが禁止されていたので、使わなくなっていた。でも今見てみたら、設定によりそれが可能になっていたので、改めて移行した。</p>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">GitLabがmasterブランチにもフォースプッシュ可能になってた。<br />右上歯車→Protected Branches→画面下ブランチの「Unprotect」</p>— 北市真 (@KitaitiMakoto) <a href="https://twitter.com/KitaitiMakoto/status/816234964412809216">2017年1月3日</a></blockquote>
<script async="" src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>GitHubは<a href="https://travis-ci.org/">Travis CI</a>や<a href="https://circleci.com/">CircleCI</a>なんかとのインテグレーションが便利だけど、GitLab.comは自前でCI機能を備えていて、YAMLファイル(<code>.gitlab-ci.yml</code>)をリポジトリーに入れておくと、その他の設定なしで自動でテストを走らせてくれる。べんり。</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">apt-get update -qq && apt-get install -y zip</span>
<span class="pi">-</span> <span class="s">ruby -v</span>
<span class="pi">-</span> <span class="s">which ruby</span>
<span class="pi">-</span> <span class="s">gem install bundler --no-document</span>
<span class="pi">-</span> <span class="s">bundle install --jobs=$(nproc) "${FLAGS[@]}"</span>
<span class="na">test:2.2</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">ruby:2.2</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">bundle exec rake test</span>
<span class="na">test:2.3</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">ruby:2.3</span>
<span class="na">script</span><span class="pi">:</span> <span class="s">bundle exec rake test</span>
<span class="na">test:2.4</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">ruby:2.4</span>
<span class="na">script</span><span class="pi">:</span> <span class="s">bundle exec rake test</span>
</code></pre></div></div>
<h2 id="heading-2017-01-08-whats-gitlab">GitLabとは</h2>
<p><a href="https://gitlab.com/">GitLab</a>は、Gitを使ったプロジェクトのホスティングウェブアプリケーションで、ざっくり言ってGitHubクローンだ。基本的な部分(<a href="https://gitlab.com/gitlab-org/gitlab-ce">Community Edition</a>)のソースコードは公開されていて、自前のサーバーにインストールして使うことができる。Enterprise Editionが有償になっている。その他にホスティングサービス(GitLab.com)もやっていて、これを今回は使った。</p>
<p>Gitリポジトリーのホスティングの他、READMEの表示、スター、フォーク、マージリクエスト(プルリクエスト)、イシュー登録、コミット履歴表示、コミット毎の差分表示、ウィキ、グループによるメンバー管理、(GitHub Pagesのような)静的ウェブサイトのホスティング、(Gistのような)スニペットなどなど、GitHubの基本的な機能は備えている(使い勝手や速度で及ばないところはある)。</p>
<p>ちょっとググった感じだと自前サーバーでGitのプロジェクトをホストするために使われる例が多いようだけど、GitHub.comの代替として使ってもいいと思う。僕は、アプリケーションのソースコードが公開されているということで、使い始めてみた。前はGitoriousが担っていた立ち位置だと思う(事実、Gitoriousがシャットダウンする時に、GitLabを案内していた)。</p>
<p>この日記はGitHub Pagesのドメインで公開しているので、移行できない。</p>
<h2 id="heading-2017-01-08-reference">追記:GitLab参考記事</h2>
<p>奇しくも最近、1/5に、Gihyo.jpでGitLabの記事が上がっていた:<br />
<a href="http://gihyo.jp/dev/column/newyear/2017/gitlab">GitLabのこれまでとこれから:新春特別企画|gihyo.jp … 技術評論社</a></p>
https://diary.kitaitimakoto.net/2017/01/08.html
リポジトリーをGitHubからGitLabに移してみた
2017-01-08T00:00:00Z
2017-01-08T00:00:00Z
<p>アドベントカレンダー「<a href="http://www.adventar.org/calendars/1885">年末年始おすすめ作品 BY.CORK Advent Calendar 2016</a>」の24日目です。</p>
<p>さて、アドベントカレンダー、クリスマスイブ担当という大役を仰せつかってしまったが(単に人がいなかっただけだが……くそっ、リア充どもめ)、実のところ、この日に相応しい作品が思い付かない。ほぼ唯一の持ちネタである今敏の『東京ゴッドファーザーズ』は、21日担当のまつおかさんに取られてしまった。</p>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">冬休みにみたい作品①東京ゴッドファーザーズ<br />3人のホームレスの主人公たちに訪れる、クリスマスの日の奇跡。いくつもの奇跡が重なって展開されるストーリーの運びと様々なドラマがみどころ!人間くさい描写や数々な愛のかたちに、あたたかい気持ちになれます。<a href="https://twitter.com/hashtag/%E3%82%B3%E3%83%AB%E3%82%AF%E3%81%8A%E3%81%99%E3%81%99%E3%82%812016?src=hash">#コルクおすすめ2016</a> <a href="https://t.co/jR5FmtH78Y">pic.twitter.com/jR5FmtH78Y</a></p>— まつおか ゆう (@108mlps) <a href="https://twitter.com/108mlps/status/811511915780288512">2016年12月21日</a></blockquote>
<script async="" src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>ディケンズの『<a href="http://www1.bbiq.jp/kareha/trans/html/christmas_carol,_a_(katokt).html">クリスマスキャロル</a>』も定番で外れが無いと思うのだが、そう言えば、半分くらいまで読んで止まっているのだった。日本に寄せて恋愛ものを選ぶとしても、僕が好きな恋愛ものジャンルは浮気・不倫なので、こんな日に相応しくない(いや、ある意味、リアルで、相応しいかも知れんが……)。</p>
<p>ということでクリスマスイブっぽいのは諦めました。アドベントカレンダーの趣旨に戻って、年末年始におすすめする物を選びます。</p>
<h2 id="section">三田紀房『ドラゴン桜』</h2>
<p><a class="magnet-viewer-embed" data-orientation="landscape" data-spread="auto" href="https://magnet.vc/v/a8zvd3s">ドラゴン桜 (1)</a>
<script src="https://magnet.vc/embed_js" defer="defer"></script></p>
<p>年末年始特に意識すると言えば、センター試験だ。僕は塾の先生から、休憩を取ることの大切さ、取るタイミングについて教えてもらって、守っていた。年末年始はきちんと休んだし、試験の前日も勉強は一切せず、のんびり、気になっていた本を読んでいた(その代わり他の日は頑張って勉強していて、特に嫌いな英語は、このままやってたら気が狂いそうだと自分で感じていた……)。大工の祖父が家の車庫にバスケットゴールを取り付けてくれたので、毎日、休憩がてら、ひたすらシュートしていた(そのお陰で、文科系部活のくせに、体育のバスケットボールでめっちゃ得点してた)。</p>
<p>このやり方が他の人にも合うかはもちろん分からないが、「この時期にはもうメンタル面が非常に大きなウェイトを占める」「メンタルは身体の影響を強く受ける」「なので休憩などを上手に(好きなだけ、ではない)取ることがとても大事」という点については衆目の一致が見られるんではないかと思う(未確認)。</p>
<p>その時期のドラマが、三田紀房の『ドラゴン桜』で描かれている。テレビ版は観ていないのと、この作品について誰かと話したこともないので、世間一般のイメージは分からないけれど、僕は「一見破天荒な受験テクニックを色々紹介しているまんが」という印象で記憶していた。ところが、コルクに入社するにあたって読み直してみたら(作者の三田紀房さんのエージェントをしています)、後半に入ると、精神面をどう受験に向けていいコンディションに持っていくかという話がメインだった。心や気持ちの問題だし、自分が通過してきたことで感情移入もしやすく、共感さるシーンが多い。</p>
<p>特に好きなのが、二人いる主人公の一人、矢島が、父親の車に乗るところ。父親のことを嫌っておりほとんど話さない矢島だけど、先生のアドバイスもあって、少し心を開きかけ、でもまた悶着あって忌避感を覚えていた頃、たまたま、家を出るタイミングが父親とかぶる。車に乗れ、送って行くと言う父親。黙って乗る矢島。そして、降りるまで、一言も話さない。「降り際に何か一言くらい言うのかな」と思って読んでいたけれど、どちらも何も発さない。コミュニケーションのないまま、車のシーンは終わる。ここでものすごく「そうだよね!」という気持ちになり、その気持ちが何なのか分からないせいで、今に至るまで記憶に残っているのだ。</p>
<p>こういう、言葉にならない気持ちを抱かせるシーンが後半には色々と出てくるのでぜひ、これから受験する人、受験したことある人に読んでほしい。ただ、そこに至るまで10巻くらいを費やしていて、上に三巻までの試し読みを貼りはしたけど、正直、前半は飛ばして10巻、11巻くらいから読むのが、(年末年始のタイミングでは)いいのではないかと思ってる。</p>
<p>最後に、自社のことなので宣伝。三田紀房さんの公式サイトはこちら。『ドラゴン桜』にちなんで受験に関する記事なんかも載っています。</p>
<p>三田紀房公式サイト - <a href="http://mitanorifusa.com/">http://mitanorifusa.com/</a></p>
<p>明日の担当は<a href="https://twitter.com/marimo_cork">中山マリモ</a>さん、アドカレの予告には「ラブ・マスターX/安野モヨコ」と書いてあって、ほう……。</p>
https://diary.kitaitimakoto.net/2016/12/24.html
アドカレ #コルクおすすめ2016 24日目 センター試験直前に、三田紀房『ドラゴン桜』後半
2016-12-24T00:00:00Z
2016-12-24T00:00:00Z
<p>アドベントカレンダー「<a href="http://qiita.com/advent-calendar/2016/groonga">Groonga Advent Calendar 2016</a>」の21日目です、書いているのは25日ですが……済みません</p>
<p>前回の<a href="18.html">Railsでの検索機能にgroonga-client-railsを使う(前編)</a>では、<a href="https://github.com/ranguba/groonga-client-rails">groonga-client-rails</a> gemを使って</p>
<ul>
<li>Groongaのデータベースを作ること</li>
<li>Railsのモデル操作とGroongaデータベースの同期を取ること</li>
</ul>
<p>をやりました。</p>
<p>後編の今日は、Groongaデータベースを使って、Railsアプリに検索機能を付けてみようと思います。</p>
<p>引き続きアプリケーションのリポジトリーをGitHubに置いています:<a href="https://github.com/KitaitiMakoto/groonga-client-rails-sample">KitaitiMakoto/groonga-client-rails-sample</a></p>
<h2 id="heading-2016-12-21-table-of-contents">目次</h2>
<ol>
<li><a href="#heading-2016-12-21-adding-route">ルーティングの追加</a></li>
<li><a href="#heading-2016-12-21-adding-search-action">検索アクションの追加</a></li>
<li><a href="#heading-2016-12-21-showing-search-result">検索結果の表示</a></li>
<li><a href="#heading-2016-12-21-adding-search-form">検索フォームの作成</a></li>
<li><a href="#heading-2016-12-21-highlighting-query">検索語のハイライト</a></li>
<li><a href="#heading-2016-12-21-advanced-search">高度な検索(カラムの指定、並び替え、ページネーション)</a></li>
</ol>
<h2 id="heading-2016-12-21-adding-route">ルーティングの追加</h2>
<p>検索用のルーティングを追加します。</p>
<ul>
<li><code>posts?q=xxx</code>と既存のコレクションリソースを使ってクエリーで検索機能を呼び出す</li>
<li><code>posts/search</code>と検索専用のリソースを追加する</li>
</ul>
<p>の二通りあり、アプリケーション全体のデザインで選ぶべき物だと思いますが、ここでは後者にします。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/routes.rb</span>
<span class="n">resources</span> <span class="ss">:posts</span> <span class="k">do</span>
<span class="n">collection</span> <span class="k">do</span>
<span class="n">get</span> <span class="ss">:search</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="heading-2016-12-21-adding-search-action">検索アクションの追加</h2>
<p><code>PostsController</code>に検索アクションを追加します。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># app/controllers/posts_controller.rb</span>
<span class="k">def</span> <span class="nf">search</span>
<span class="n">searcher</span> <span class="o">=</span> <span class="no">PostsSearcher</span><span class="p">.</span><span class="nf">new</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:q</span><span class="p">]</span>
<span class="k">if</span> <span class="n">query</span><span class="p">.</span><span class="nf">blank?</span>
<span class="n">redirect_to</span> <span class="ss">action: </span><span class="s2">"index"</span>
<span class="k">return</span>
<span class="k">end</span>
<span class="vi">@posts</span> <span class="o">=</span> <span class="n">searcher</span><span class="p">.</span><span class="nf">search</span><span class="p">.</span>
<span class="nf">query</span><span class="p">(</span><span class="n">query</span><span class="p">).</span>
<span class="nf">result_set</span><span class="p">.</span><span class="nf">records</span>
<span class="k">end</span>
</code></pre></div></div>
<p>(モデルの代わりに)サーチャークラスをインスタンス化し、クエリーを組み立てていきます。</p>
<p>検索の開始には<code>#search</code>メソッドを呼び出します。これでクエリー組み立ての準備が整います(クエリーオブジェクトが返されます)。</p>
<p><code>query</code>メソッドに文字列を渡すことで、検索語を認識させます。</p>
<p><code>result_set</code>を呼ぶとリモートのGroongaサーバーにHTTPリクエストを送って検索結果を取得します。</p>
<p><code>records</code>によって、それをRubyのオブジェクトに変換して返します。</p>
<h2 id="heading-2016-12-21-showing-search-result">検索結果の表示</h2>
<p>検索結果を表示します。<code>app/views/posts/index.html.erb</code>を<code>app/views/posts/search.html.erb</code>にコピーし、ActiveModel依存の所を書き換えます。</p>
<div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- app/views/posts/search.html.erb --></span>
<span class="nt"><h1></span>Posts<span class="nt"></h1></span>
<span class="nt"><table></span>
<span class="nt"><thead></span>
<span class="nt"><tr></span>
<span class="nt"><th></span>Title<span class="nt"></th></span>
<span class="nt"><th></span>Body<span class="nt"></th></span>
<span class="nt"><th</span> <span class="na">colspan=</span><span class="s">"3"</span><span class="nt">></th></span>
<span class="nt"></tr></span>
<span class="nt"></thead></span>
<span class="nt"><tbody></span>
<span class="cp"><%</span> <span class="vi">@posts</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">post</span><span class="o">|</span> <span class="cp">%></span>
<span class="nt"><tr></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">post</span><span class="p">.</span><span class="nf">title</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">post</span><span class="p">.</span><span class="nf">body</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'Show'</span><span class="p">,</span> <span class="n">post_path</span><span class="p">(</span><span class="n">extract_id</span><span class="p">(</span><span class="n">post</span><span class="p">))</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'Edit'</span><span class="p">,</span> <span class="n">edit_post_path</span><span class="p">(</span><span class="n">extract_id</span><span class="p">(</span><span class="n">post</span><span class="p">))</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'Destroy'</span><span class="p">,</span> <span class="n">post_path</span><span class="p">(</span><span class="n">extract_id</span><span class="p">(</span><span class="n">post</span><span class="p">)),</span> <span class="ss">method: :delete</span><span class="p">,</span> <span class="ss">data: </span><span class="p">{</span> <span class="ss">confirm: </span><span class="s1">'Are you sure?'</span> <span class="p">}</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
<span class="nt"></tbody></span>
<span class="nt"></table></span>
<span class="nt"><br></span>
<span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'New Post'</span><span class="p">,</span> <span class="n">new_post_path</span> <span class="cp">%></span>
</code></pre></div></div>
<p><code>extract_id</code>は、レコードデータからIDを取り出すヘルパーです。これも自分で定義します。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">PostsHelper</span>
<span class="k">def</span> <span class="nf">extract_id</span><span class="p">(</span><span class="n">post</span><span class="p">)</span>
<span class="n">post</span><span class="p">[</span><span class="s2">"_key"</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="s2">"-"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Groongaでは、<code>_key</code>というカラムによって、レコードを一意に特定するのですが、groonga-client-rails(のデフォルト)では、「モデル名-連番」となる(<code>Post-1</code>)ので、そこからAcitiveRecordのIDに変換しています。</p>
<p>検索フォームはありませんが、これで一応機能はできました。http://localhost:3000/posts/search?q=Qiita などにアクセスすると、検索結果が見られると思います。</p>
<h2 id="heading-2016-12-21-adding-search-form">検索フォームの作成</h2>
<p>次にフォームです。<code>/posts/search</code>に<code>q</code>クエリー付きでGETアクセスを投げるだけなので簡単です。</p>
<div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- app/views/posts/_search_form.html.erb --></span>
<span class="cp"><%=</span> <span class="n">form_tag</span><span class="p">(</span><span class="n">search_posts_path</span><span class="p">,</span> <span class="ss">method: </span><span class="s2">"get"</span><span class="p">)</span> <span class="k">do</span> <span class="cp">%></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">search</span> <span class="na">name=</span><span class="s">q</span> <span class="na">value=</span><span class="s">"</span><span class="cp"><%=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:q</span><span class="p">]</span> <span class="cp">%></span><span class="s">"</span> <span class="na">required</span><span class="nt">></span>
<span class="cp"><%=</span> <span class="n">submit_tag</span><span class="p">(</span><span class="s2">"Search"</span><span class="p">)</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre></div></div>
<p>これをそれぞれのテンプレートファイルに埋め込んでやります(省略)。</p>
<h2 id="heading-2016-12-21-highlighting-query">検索語のハイライト</h2>
<p>ただ、せっかくだから、検索語が分かりやすくなっていてほしいですよね。また、メモの全文をここで表示してしまうと、長過ぎるという場合もあると思います。両方をいっぺんに解決できる方法として、Groongaの<code>snippet_html</code>関数があります(<a href="http://groonga.org/ja/docs/reference/functions/snippet_html.html">7.14.17. snippet_html</a>)。</p>
<p>これは検索語の周辺数十文字(スニペット)を返してくれる関数です。更に、検索語を<code><span class="keyword">...</span></code>でマークアップしてくれます(HTML)。</p>
<p><code>snippet_html</code>を使うには、Groongaから取得するカラムにこれを指定します。groonga-client-railsはデフォルトで、モデルで設定したカラムを取得してくれます(なので<code>title</code>と<code>body</code>が取れていた)。これをカスタマイズするには、クエリーに<code>output_columns</code>というパラメーターを追加する必要があります(<a href="http://groonga.org/ja/docs/reference/commands/select.html#output-columns">7.3.54.4.4.1. output_columns</a>)。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># app/controllers/posts_controller.rb</span>
<span class="vi">@posts</span> <span class="o">=</span> <span class="n">searcher</span><span class="p">.</span><span class="nf">search</span><span class="p">.</span>
<span class="nf">query</span><span class="p">(</span><span class="n">query</span><span class="p">).</span>
<span class="nf">output_columns</span><span class="p">(</span><span class="s1">'_key,title,snippet_html(body)'</span><span class="p">).</span>
<span class="nf">result_set</span><span class="p">.</span><span class="nf">records</span>
</code></pre></div></div>
<p>これで、「<code>body</code>カラムでの検索結果にはスニペットを取得する」という意味になります。</p>
<p>これに合わせてビューも変えなくてはいけません。</p>
<div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><td></span><span class="cp"><%=</span><span class="o">=</span> <span class="n">post</span><span class="p">.</span><span class="nf">snippet_html</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"<br>"</span><span class="p">)</span> <span class="cp">%></span><span class="nt"></td></span>
</code></pre></div></div>
<p>HTMLを埋め込むので<code>=</code>を<code>==</code>にしています。また、結果は配列になっているので(一つのメモの離れた所に検索語がある場合、それぞれの周辺を取得します)改行で接続します。</p>
<p>先述の通り、検索語はマークアップされるので、スタイリングしましょう。</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* app/assets/stylesheets/posts.scss */</span>
<span class="nc">.keyword</span> <span class="p">{</span>
<span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bolder</span><span class="p">;</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>これで、検索語が赤い太字になりました。</p>
<p>何とか見られる結果になったんではないでしょうか。
<img src="https://gyazo.com/2cc4ffc1487555bbd51fe768f6c7fcac.png" alt="" /></p>
<h2 id="heading-2016-12-21-advanced-search">高度な検索(カラムの指定、並び替え、ページネーション)</h2>
<p>Groongaでは、検索の際に様々な条件を付け加えたり、結果を加工したりできます。機能の詳細はドキュメント(<a href="http://groonga.org/ja/docs/reference/commands/select.html">7.3.54. select</a>)に譲りますが、ここでは以下の三つに対応してみましょう。</p>
<dl>
<dt>match_columns</dt>
<dd>検索に使用するカラムを試定。例えば「検索語がタイトルに含まれる場合のみ表示する」など。</dd>
<dt>sortby</dt>
<dd>指定したカラムで並び替える。</dd>
<dt>paginate</dt>
<dd>検索結果が多過ぎる場合にページネーションします。</dd>
</dl>
<p>と言っても簡単で、クエリーオブジェクトから、それぞれのメソッドを呼び出すだけです。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># app/controllers/posts_controller.rb</span>
<span class="vi">@posts</span> <span class="o">=</span> <span class="n">searcher</span><span class="p">.</span><span class="nf">search</span><span class="p">.</span>
<span class="nf">query</span><span class="p">(</span><span class="n">query</span><span class="p">).</span>
<span class="nf">output_columns</span><span class="p">(</span><span class="s1">'_key,title,snippet_html(body)'</span><span class="p">)</span>
<span class="p">[</span><span class="ss">:match_columns</span><span class="p">,</span> <span class="ss">:sortby</span><span class="p">,</span> <span class="ss">:paginate</span><span class="p">].</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">param</span><span class="o">|</span>
<span class="k">if</span> <span class="n">params</span><span class="p">[</span><span class="n">param</span><span class="p">].</span><span class="nf">present?</span>
<span class="vi">@posts</span> <span class="o">=</span> <span class="vi">@posts</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">param</span><span class="p">,</span> <span class="n">params</span><span class="p">[</span><span class="n">param</span><span class="p">])</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="vi">@posts</span> <span class="o">=</span> <span class="vi">@posts</span><span class="p">.</span><span class="nf">result_set</span><span class="p">.</span><span class="nf">records</span>
</code></pre></div></div>
<p>これにフォームを対応させれば出来上がりです。</p>
<p>例えば、タイトルで並び替えした場合はこうなります。
<img src="https://gyazo.com/969feded70e51520aab4962369291908.png" alt="" /></p>
<p>並び順を逆にするには、カラム名の前にマイナス記号(<code>-</code>)を付けます。
<img src="https://gyazo.com/b117f1fc42863df60684437bcc2298c5.png" alt="" /></p>
<p>どうでしたか、groonga-client-railsは、無理にActiveModel風にしない所が気に入っていたりします……と言っている間に、開発者の<a href="https://github.com/kou">@kou</a>さんがよりちゃんとした記事を書いていました、締め切り破って済みませんでした……。</p>
<p>» <a href="http://www.clear-code.com/blog/2016/12/22.html">Ruby on RailsでMySQL・PostgreSQL・SQLite3とGroongaを使って日本語全文検索を実現する方法</a></p>
https://diary.kitaitimakoto.net/2016/12/21.html
Railsでの検索機能にgroonga-client-railsを使う(後編)
2016-12-21T00:00:00Z
2016-12-21T00:00:00Z
<p>今日の日記は<a href="http://qiita.com/advent-calendar/2016/groonga">Groonga Advent Calendar 2016</a>の18日目です。</p>
<p>今日はRailsアプリケーションに検索機能を付けるのに、groonga-client-rails gemを使う方法を、サンプルアプリケーションを作りながら紹介したいと思います。長くなったので……というか、準備と書く時間の見積もりを誤ったので、前後編に分けます。前編の今日はインストールからRailsのモデルと検索機能を結び付ける(言い回しが分かりにくいと思いますがあとで分かります)まで紹介し、後編では検索用のUIを作ろうと思います。</p>
<p>以下の環境で動作確認をしています。</p>
<ul>
<li>OS … OS X El Capitan 10.11.6</li>
<li>Ruby … 2.3.3</li>
<li>Groonga … 6.1.1</li>
<li>Ruby on Rails … 5.0.0.1</li>
<li>groonga-client-rails … 0.9.4</li>
</ul>
<h2 id="heading-2016-12-18-table-of-contents">目次</h2>
<ol>
<li><a href="#heading-2016-12-18-groonga-client-rails">groonga-client-railsとは</a></li>
<li><a href="#heading-2016-12-18-about-sample-app">サンプルアプリケーションについて</a></li>
<li><a href="#heading-2016-12-18-installing-active-groonga-rails">インストール</a></li>
<li><a href="#heading-2016-12-18-scaffolding">基本機能の作成</a></li>
<li><a href="#heading-2016-12-18-tie-app-and-search-feature">検索機能との結び付け</a>
<ol>
<li><a href="#heading-2016-12-18-booing-groonga">Groongaの起動</a></li>
<li><a href="#heading-2016-12-18-creating-searcher">サーチャークラスの作成</a></li>
<li><a href="#heading-2016-12-18-tie-model-and-searcher">モデルクラスとサーチャークラスとの結び付け</a></li>
<li><a href="#heading-2016-12-18-synchronizing-data">データの同期</a></li>
</ol>
</li>
<li><a href="#heading-2016-12-18-appendix-searching">おまけ - 検索して遊ぶ</a></li>
</ol>
<h2 id="heading-2016-12-18-groonga-client-rails">groonga-client-railsとは</h2>
<p><a href="https://github.com/ranguba/groonga-client-rails">groonga-client-rails</a>は、<a href="http://rubyonrails.org/">Ruby on Rails</a>に検索用の機能を提供するgemです。バックエンドの検索エンジンに<a href="http://groonga.org/ja/">Groonga</a>を使っています。以前、RailsにGroongaの検索機能を提供するgemとして<a href="https://github.com/ranguba/activegroonga">ActiveGroonga</a>を紹介しましたが(<a href="http://qiita.com/KitaitiMakoto/items/e518a51a804896f9f062">RailsでActiveGroongaを使う</a>)、groonga-client-railsはこれとは別アプローチのgemです。</p>
<p>ActiveGroonga(が内部で使っている<a href="http://ranguba.org/rroonga/ja/">Rroonga</a>)はローカルのファイルシステムにGroongaのデータベースを作成し、そこにC API経由でアクセスします。このため、インストール時にCのコンパイルが走り、RailsサーバーとGroongaデータベースは同じマシン上に存在する必要がありました。スケールアウトさせる際にはGroongaデータベースの同期が課題にもなります。</p>
<p>groonga-client-railsが使うGroongaはリモートサーバーです。GroongaはC API経由でアクセスするほかに、HTTPサーバーとして動作し、HTTPクライアントでアクセスしてデータの投入と検索を行うこともできます(GQTPというGroonga独自のプロトコルも使えます)。この用途に向けた<a href="https://github.com/ranguba/groonga-client">groonga-client</a>というgemがあり、それをRailsで使いやすくしたのがgroonga-client-railsです。</p>
<p>groonga-client-railsでは、<a href="http://guides.rubyonrails.org/active_job_basics.html">ActiveJob</a>の<code>XxxJob</code>、<a href="https://github.com/carrierwaveuploader/carrierwave">CarrierWave</a>の<code>XxxUploader</code>のような<code>XxxSearcher</code>というサーチャークラスを作って検索エンジンへのアクセスを抽象化します。サーチャーの作り方は後述しますが、モデルクラス一つと対応付けられ、モデルの(RDBMSやMongoDBなどへの)保存・更新・削除とGroongaの対応テーブルとの同期を取ったり、対応テーブルからの検索用APIを提供したりします。うまくインターフェイスを揃えればElsticsearchとかも同じクラスで扱えるかも?</p>
<h2 id="heading-2016-12-18-about-sample-app">サンプルアプリケーションについて</h2>
<p>サンプルとして、SQLite3のpostsテーブルにメモを入れたり読んだりするだけの簡単なRailsアプリケーションを使って、groonga-client-railsを導入していきます。</p>
<p>ソースコードはこちら:
<a href="https://github.com/KitaitiMakoto/groonga-client-rails-sample">KitaitiMakoto/groonga-client-rails-sample</a></p>
<p>上で「groonga-client-railsが使うGroongaはリモートサーバーです。」と書きましたが、今回はGroongaもRailsも同じローカルマシンで動かして、127.0.0.1でHTTPでアクセスさせようと思います。</p>
<p>なお、アプリケーション作成においては<a href="https://github.com/ranguba/groonga-client-rails/tree/master/test/apps">groonga-client-railsのテストディレクトリー</a>を大いに参考しました。ほぼまんまです。</p>
<h2 id="heading-2016-12-18-installing-active-groonga-rails">インストール</h2>
<p>Groongaのインストールは公式サイトのドキュメント(<a href="http://groonga.org/ja/docs/install.html">2. インストール</a>)を見ながらやってください。日本語を扱うと思うので、MeCabトークナイザーもインストールしておくといいと思います(これもドキュメントに記載があります)。</p>
<p>OS XであればHomebrewでインストールできます:</p>
<pre><code>% brew install groonga --with-mecab
% brew install mecab-ipadic
</code></pre>
<p>groonga-client-railsのインストールは、いつも通りGemfileに</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">gem</span> <span class="s1">'groonga-client-rails'</span>
</code></pre></div></div>
<p>と書いて</p>
<pre><code>% bundle install --path=vendor/bundle
</code></pre>
<p>です。</p>
<p>軽く確認しておきましょう。</p>
<pre><code>% ./bin/rails --help | grep groonga
groonga:sync # Synchronize Groonga database with model data
</code></pre>
<p>タスクが追加されていますね。後で実際に使ってみます。</p>
<h2 id="heading-2016-12-18-scaffolding">基本機能の作成</h2>
<p>postsを読み書きするための土台を作ります。</p>
<pre><code>% ./bin/rails generate scaffold post title:string body:text
Expected string default value for '--jbuilder'; got true (boolean)
invoke active_record
create db/migrate/20161217142208_create_posts.rb
create app/models/post.rb
invoke test_unit
create test/models/post_test.rb
create test/fixtures/posts.yml
invoke resource_route
route resources :posts
invoke scaffold_controller
create app/controllers/posts_controller.rb
invoke erb
create app/views/posts
create app/views/posts/index.html.erb
create app/views/posts/edit.html.erb
create app/views/posts/show.html.erb
create app/views/posts/new.html.erb
create app/views/posts/_form.html.erb
invoke test_unit
create test/controllers/posts_controller_test.rb
invoke helper
create app/helpers/posts_helper.rb
invoke test_unit
invoke jbuilder
create app/views/posts/index.json.jbuilder
create app/views/posts/show.json.jbuilder
create app/views/posts/_post.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/posts.coffee
invoke scss
create app/assets/stylesheets/posts.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss
Generate posts scaffold
</code></pre>
<p>追加されたファイルを見てみます。</p>
<pre><code>% git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: config/routes.rb
Untracked files:
(use "git add <file>..." to include in what will be committed)
app/assets/javascripts/posts.coffee
app/assets/stylesheets/posts.scss
app/assets/stylesheets/scaffolds.scss
app/controllers/posts_controller.rb
app/helpers/posts_helper.rb
app/models/post.rb
app/views/posts/
config/groonga_client.yml
db/migrate/
test/controllers/posts_controller_test.rb
test/fixtures/posts.yml
test/models/post_test.rb
no changes added to commit (use "git add" and/or "git commit -a")
</code></pre>
<p><code>config/groonga_client.yml</code>というファイルが出来ていますね(ジェネレーターのアウトプットには出て来ないので、見逃しそうでした)。中身はこうです。</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">default</span><span class="pi">:</span> <span class="nl">&default</span>
<span class="na">protocol</span><span class="pi">:</span> <span class="s">http</span>
<span class="c1"># protocol: https</span>
<span class="na">host</span><span class="pi">:</span> <span class="s">127.0.0.1</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">10041</span>
<span class="c1"># user: alice</span>
<span class="c1"># password: secret</span>
<span class="na">read_timeout</span><span class="pi">:</span> <span class="s">-1</span>
<span class="c1"># read_timeout: 3</span>
<span class="na">backend</span><span class="pi">:</span> <span class="s">synchronous</span>
<span class="na">development</span><span class="pi">:</span>
<span class="na"><<</span><span class="pi">:</span> <span class="nv">*default</span>
<span class="na">test</span><span class="pi">:</span>
<span class="na"><<</span><span class="pi">:</span> <span class="nv">*default</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">20041</span>
<span class="na">production</span><span class="pi">:</span>
<span class="na"><<</span><span class="pi">:</span> <span class="nv">*default</span>
<span class="na">host</span><span class="pi">:</span> <span class="s">127.0.0.1</span>
<span class="na">read_timeout</span><span class="pi">:</span> <span class="m">10</span>
</code></pre></div></div>
<p>何となく分かると思います。10041番ポートは、GroongaのHTTPサーバーを起動する時のデフォルトポートです。</p>
<p>Groongaについては一先ず置いておいて、RDB(SQLite3)の作成とマイグレーションをします。</p>
<pre><code>% ./bin/rails db:migrate
== 20161217142208 CreatePosts: migrating ======================================
-- create_table(:posts)
-> 0.0014s
== 20161217142208 CreatePosts: migrated (0.0015s) =============================
</code></pre>
<p>http://localhost:3000/posts にアクセスすると、アプリケーションが動いているのが確認できると思います。</p>
<p style="overflow: auto; border: solid 1px lightgray;">
<img alt="入力画面" src="https://gyazo.com/fcdf0c0bcc926d745705d71855430643.png" style="float: left; box-sizing: border-box; width: 50%; border-right: dashed 1px lightgray;" /><img alt="入力結果" src="https://gyazo.com/96b03a960a84f5c92564cc2a23211e51.png" style="width: 50%;" />
</p>
<h2 id="heading-2016-12-18-tie-app-and-search-feature">検索機能との結び付け</h2>
<h3 id="heading-2016-12-18-booing-groonga">Groongaの起動</h3>
<p>サンプルアプリと検索機能を結び付ける前に、まずGroongaを起動しておきましょう。</p>
<pre><code>% groonga --protocol http -s -n db/groonga.db
</code></pre>
<p><code>-n</code>は「新しくデータベースを作る」というオプションで、Groongaデータベースがまだない時に一度だけ指定します。二度目以降は不要なので</p>
<pre><code>% groonga --protocol http -s db/groonga.db
</code></pre>
<p>として起動します。</p>
<p>終了させるには<kbd><kbd>Ctrl</kbd>+<kbd>C</kbd></kbd>でSIGINTを送ります(デーモンモードなどについてはドキュメントを見てください)。</p>
<p><code>db/groonga.db</code> は指定したパス(及びそれをプリフィクスとしたパス)にデータベースファイルを作成するという意味です。Railsのdbディレクトリーを指定しています。</p>
<p>サーバーモードのGroongaは、検索用のAPIの他に人間用の管理インターフェイスも持っています。http://localhost:10041/ にアクセスしてみてください。</p>
<p><img src="https://gyazo.com/6af787d2595fc2ea9e17b9da53f442b0.png" alt="Groonga管理画面" /></p>
<p>まだテーブルの作成すらしていないので、左側の「List of table」欄には何もありません。</p>
<h3 id="heading-2016-12-18-creating-searcher">サーチャークラスの作成</h3>
<p>RailsアプリとGroongaとの結び付けをするサーチャークラスを作成します。サーチャークラスはモデルクラス一つにつき一つまで作成することができます<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>。不要なモデルについては、当然作る必要はありません。</p>
<p>ここでは<code>Post</code>モデルしかないのでこれに対応するクラスを作成するのですが、他の種類のクラスの例に漏れず、まず<code>app/searchers/application_searcher.rb</code>ファイルに<code>ApplicationSearcher</code>クラスを作りましょう。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ApplicationSearcher</span> <span class="o"><</span> <span class="no">Groonga</span><span class="o">::</span><span class="no">Client</span><span class="o">::</span><span class="no">Searcher</span>
<span class="k">end</span>
</code></pre></div></div>
<p>次に<code>PostsSearcher</code>クラスを<code>app/searchers/posts_searcher.rb</code>に作ります。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">PostsSearcher</span> <span class="o"><</span> <span class="no">ApplicationSearcher</span>
<span class="n">schema</span><span class="p">.</span><span class="nf">column</span> <span class="ss">:title</span><span class="p">,</span> <span class="p">{</span>
<span class="ss">type: </span><span class="s2">"ShortText"</span><span class="p">,</span>
<span class="ss">index: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">index_type: :full_text_search</span>
<span class="p">}</span>
<span class="n">schema</span><span class="p">.</span><span class="nf">column</span> <span class="ss">:body</span><span class="p">,</span> <span class="p">{</span>
<span class="ss">type: </span><span class="s2">"Text"</span><span class="p">,</span>
<span class="ss">index: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">index_type: :full_text_search</span>
<span class="p">}</span>
<span class="n">schema</span><span class="p">.</span><span class="nf">column</span> <span class="ss">:updated_at</span><span class="p">,</span> <span class="p">{</span>
<span class="ss">type: </span><span class="s2">"Time"</span><span class="p">,</span>
<span class="ss">index: </span><span class="kp">true</span>
<span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>
<p>何となく分かると思いますが、<code>schema.column</code>メソッドの第一引数(<code>:title</code>、<code>:body</code>、<code>:updated_at</code>)は、Groonga上の検索条件や結果に使うカラムです(Groongaもカラムとレコードでデータを管理する、テーブル型のデータベースです)。このカラムに検索語が含まれているかとか、範囲内に収まっているかとかで検索することになります。これからGroongaのテーブル上に作成するカラム名なので、RDBMSのカラム名とは違っていてもいいのですが、同じにしておく方が分かりやすいでしょう。</p>
<p>第二引数でそのカラムの性質を定義します。<code>type</code>はGroongaのデータ型をします。大体分かると思うので、このまま進めましょう。実際に使う時には公式ドキュメント(<a href="http://groonga.org/ja/docs/reference/types.html">7.4. データ型</a>)を参照してください。</p>
<p><code>index</code>はインデックスを張るかどうかで、RDBMSと同様、張れば検索やソートが速くなるし、張らなければストレージやメモリーを節約できます。検索エンジンを導入するという時点で、殆どのカラムに<code>true</code>を指定することになると思います。「検索結果表示には使うけど、検索条件には使わない」という付随的な情報のカラムでだけ<code>false</code>にしておきます。</p>
<p><code>index_type</code>は、<strong>全文</strong>検索に使うカラムでだけ<code>:full_text_search</code>を指定します。それ以外の場合は無くて構いません。例えば<code>:updated_at</code>(日時)は範囲指定に使うことはあっても全文検索には使わないので、ここでも指定されていません。</p>
<p>他に<code>vector</code>を<code>true</code>か<code>false</code>で指定することができるようで、Groongaのベクター型に対応すると思われます。ベクター型についても詳細はドキュメントを参照してください。「投稿に複数タグを付けられる場合の、タグ」など、配列のように複数の値が入るカラムをベクターにします(この日記のタグもベクターとしてGroonga上のカラムにしています)。</p>
<p>本来、Groongaのテーブルではもっと細かなチューニングができますが、groonga-client-railsでは(今の所)ライブラリーのおすすめ設定を使うことになります。</p>
<h3 id="heading-2016-12-18-tie-model-and-searcher">モデルクラスとサーチャークラスとの結び付け</h3>
<p>次に、<code>Post</code>モデルに設定を書いて<code>PostsSearcher</code>と結び付けます。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Post</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">searcher</span> <span class="o">=</span> <span class="no">PostsSearcher</span><span class="p">.</span><span class="nf">source</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="n">searcher</span><span class="p">.</span><span class="nf">title</span> <span class="o">=</span> <span class="ss">:title</span>
<span class="n">searcher</span><span class="p">.</span><span class="nf">body</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="p">{</span>
<span class="n">model</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">gsub</span><span class="p">(</span><span class="sr">/<.*?>/</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">searcher</span><span class="p">.</span><span class="nf">updated_at</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">end</span>
</code></pre></div></div>
<p>この設定をすることでモデルクラスの<code>after_create</code>、<code>after_update</code>、<code>after_destroy</code>フックにコールバックが登録されて、リモートのGroongaサーバーと同期が取れるようになります(なので、これらのフックさえあれば<code>ActiveRecord</code>以外のクラスでも結び付けられます)。</p>
<p>ここで<code>searcher</code>の属性ライター(<code>title=</code>、<code>body=</code>、<code>updated_at=</code>)は、Groongaデータベース側のカラム名を意味しています。右辺には</p>
<ul>
<li><code>Symbol</code></li>
<li><code>TrueClass</code></li>
<li><code>NilClass</code></li>
<li><code>Proc</code>など<code>#call</code>メソッドを持つオブジェクト</li>
</ul>
<p>のいずれかを指定できます。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">searcher</span><span class="p">.</span><span class="nf">updated_at</span> <span class="o">=</span> <span class="kp">true</span>
</code></pre></div></div>
<p>のように<code>TrueClass</code>の場合は、モデルクラス(<code>Post</code>、<code>posts</code>テーブル)の、左辺と同名の属性・カラム(<code>updated_at</code>)の値を取得して、その値をGroongaデータベースと同期します。なので実は</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">searcher</span><span class="p">.</span><span class="nf">title</span> <span class="o">=</span> <span class="ss">:title</span>
</code></pre></div></div>
<p>も</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">searcher</span><span class="p">.</span><span class="nf">title</span> <span class="o">=</span> <span class="kp">true</span>
</code></pre></div></div>
<p>で構いません。</p>
<p><code>Symbol</code>は、「RDBMSのカラム名とGroongaデータベースでのカラム名が違う」といった場合に活きます。</p>
<p><code>Proc</code>の場合は見れば分かると思います。<code>#call</code>の戻り値がGroongaデータベースの該当カラムに挿入されます。ここではHTMLタグっぽい文字列を削除しています。</p>
<p>最後に<code>nil</code>の場合は、そのカラムはGroongaデータベースに入りません。「始めは使っていたけど後から使わなくなったカラム」なんかで使うんでしょうかね。</p>
<h3 id="heading-2016-12-18-synchronizing-data">データの同期</h3>
<p>さて、これでRDBMS(SQLite3)とGroongaとでデータの同期が取れるようになりました—これからのデータについては。</p>
<p>この時点ではRailsを再起動したりしても既存のデータが同期されません。既存データを一括同期するためには、groonga-client-railsが提供するRakeタスクを使います。</p>
<pre><code>% ./bin/rails groonga:sync
</code></pre>
<p>これを実行してGroonga管理画面(http://localhost:10041/)にアクセスし「List table」をクリックすると、<code>posts</code>など今定義したテーブルが出来ているのが分かります。</p>
<p><img src="https://gyazo.com/2a7c4aeec6463385d813995dba71d185.png" alt="既存のデータがGroongaに同期されてpostsテーブルなどが作成されている" /></p>
<p><code>posts</code>の「Detail」ボタンを押すと、既存のレコードが同期されているのが分かります。</p>
<p>通常のモデルの操作でデータが同期されるのも見ておきましょう。Railsのscaffoldで出来たUIで、データを追加します。</p>
<p><img src="https://gyazo.com/7263c673910e5c30d519c93a5cd36c7b.png" alt="Railsアプリでレコードを追加する" /></p>
<p>管理画面を見ると、Groongaにも対応するデータが入っています。</p>
<p><img src="https://gyazo.com/24979865cd7687e14bcd2f1601633382.png" alt="追加したデータがGroongaに同期されている" /></p>
<h2 id="heading-2016-12-18-appendix-searching">おまけ - 検索して遊ぶ</h2>
<p>今日はここまで。次回は検索用のUIを導入しようと思います。</p>
<p>が、効果を実感しにくい、地道な作業ばかりだったので、少しだけ遊んでみましょう。今日の作業分で、コンソールで検索ができるようになっています。</p>
<pre><code>% ./bin/rails console
</code></pre>
<p>検索では<code>PostsSearcher#search</code>メソッドから開始して(<code>where</code>のように)検索クエリーを組み立て、<code>result_set</code>でGroongaへアクセスして結果を取得します。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">searcher</span> <span class="o">=</span> <span class="no">PostsSearcher</span><span class="p">.</span><span class="nf">new</span>
<span class="n">searcher</span><span class="p">.</span><span class="nf">search</span><span class="p">.</span><span class="nf">result_set</span>
<span class="o">=></span> <span class="c1">#<Groonga::Client::Searcher::Select::ResultSet:0x007fd7c4b0f7e0 @response=#<Groonga::Client::Response::Select:0x007fd7c4b1d318 @command=#<Groonga::Command::Select:0x007fd7c4adf388 @command_name="select", @arguments={:table=>"posts", :match_columns=>"title, body"}, @original_format=nil, @original_source=nil, @path_prefix="/d/", @slices={}, @drilldowns=[], @labeled_drilldowns={}>, @header=[0, 1481988639.462895, 0.0001811981201171875], @body=[[[2], [["_id", "UInt32"], ["_key", "ShortText"], ["body", "Text"], ["title", "ShortText"], ["updated_at", "Time"]], [1, "Post-2", "Railsで検索機能を追加する選択肢の一つに、ActiveGroongaで、ActiveRecordのように追加する方法がありますが、これはローカルのデータベースファイルにアクセスするため、スケーラビリティ等に不安がありました。groonga-client-railsなら、リモートのGroongaサーバーにHTTPやGQTPでアクセスできるので、スケーラビリティや保守性をDBやアプリケーションサーバーと別々に考えることができます。", "groonga-client-railsについて", 1481954320.0], [2, "Post-3", "ActiveGroongaは「SQLite3でActiveRecordを使う」のに似ています。詳しくは過去のQiitaの記事などを見てください。", "ActiveGroongaについて", 1481955601.0]]], @n_hits=2, @records=[{"_id"=>1, "_key"=>"Post-2", "body"=>"Railsで検索機能を追加する選択肢の一つに、ActiveGroongaで、ActiveRecordのように追加する方法がありますが、これはローカルのデータベースファイルにアクセスするため、スケーラビリティ等に不安がありました。groonga-client-railsなら、リモートのGroongaサーバーにHTTPやGQTPでアクセスできるので、スケーラビリティや保守性をDBやアプリケーションサーバーと別々に考えることができます。", "title"=>"groonga-client-railsについて", "updated_at"=>2016-12-17 14:58:40 +0900}, {"_id"=>2, "_key"=>"Post-3", "body"=>"ActiveGroongaは「SQLite3でActiveRecordを使う」のに似ています。詳しくは過去のQiitaの記事などを見てください。", "title"=>"ActiveGroongaについて", "updated_at"=>2016-12-17 15:20:01 +0900}], @slices={}, @drilldowns=[], @raw="[[0,1481988639.462895,0.0001811981201171875],[[[2],[[\"_id\",\"UInt32\"],[\"_key\",\"ShortText\"],[\"body\",\"Text\"],[\"title\",\"ShortText\"],[\"updated_at\",\"Time\"]],[1,\"Post-2\",\"Railsで検索機能を追加する選択肢の一つに、ActiveGroongaで、ActiveRecordのように追加する方法がありますが、これはローカルのデータベースファイルにアクセスするため、スケーラビリティ等に不安がありました。groonga-client-railsなら、リモートのGroongaサーバーにHTTPやGQTPでアクセスできるので、スケーラビリティや保守性をDBやアプリケーションサーバーと別々に考えることができます。\",\"groonga-client-railsについて\",1481954320.0],[2,\"Post-3\",\"ActiveGroongaは「SQLite3でActiveRecordを使う」のに似ています。詳しくは過去のQiitaの記事などを見てください。\",\"ActiveGroongaについて\",1481955601.0]]]]">></span>
<span class="n">searcher</span><span class="p">.</span><span class="nf">search</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="s1">'Qiita'</span><span class="p">).</span><span class="nf">result_set</span>
<span class="o">=></span> <span class="c1">#<Groonga::Client::Searcher::Select::ResultSet:0x007fd7c66d14b0 @response=#<Groonga::Client::Response::Select:0x007fd7c66d1f00 @command=#<Groonga::Command::Select:0x007fd7c40cb400 @command_name="select", @arguments={:table=>"posts", :match_columns=>"title, body", :query=>"Qiita"}, @original_format=nil, @original_source=nil, @path_prefix="/d/", @slices={}, @drilldowns=[], @labeled_drilldowns={}>, @header=[0, 1481988733.769218, 0.1913049221038818], @body=[[[1], [["_id", "UInt32"], ["_key", "ShortText"], ["body", "Text"], ["title", "ShortText"], ["updated_at", "Time"]], [2, "Post-3", "ActiveGroongaは「SQLite3でActiveRecordを使う」のに似ています。詳しくは過去のQiitaの記事などを見てください。", "ActiveGroongaについて", 1481955601.0]]], @n_hits=1, @records=[{"_id"=>2, "_key"=>"Post-3", "body"=>"ActiveGroongaは「SQLite3でActiveRecordを使う」のに似ています。詳しくは過去のQiitaの記事などを見てください。", "title"=>"ActiveGroongaについて", "updated_at"=>2016-12-17 15:20:01 +0900}], @slices={}, @drilldowns=[], @raw="[[0,1481988733.769218,0.1913049221038818],[[[1],[[\"_id\",\"UInt32\"],[\"_key\",\"ShortText\"],[\"body\",\"Text\"],[\"title\",\"ShortText\"],[\"updated_at\",\"Time\"]],[2,\"Post-3\",\"ActiveGroongaは「SQLite3でActiveRecordを使う」のに似ています。詳しくは過去のQiitaの記事などを見てください。\",\"ActiveGroongaについて\",1481955601.0]]]]">></span>
</code></pre></div></div>
<p>後編ではこれを使って検索UIを組み立てます。</p>
<p>追記。</p>
<p>後編を書きました:<a href="21.html">Railsでの検索機能にgroonga-client-railsを使う(後編)</a></p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>正確には二つ以上作れますが、あまり意味がないと思います。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>
https://diary.kitaitimakoto.net/2016/12/18.html
Railsでの検索機能にgroonga-client-railsを使う(前編)
2016-12-18T00:00:00Z
2016-12-18T00:00:00Z
<p>この日記では<a href="https://www.polymer-project.org/">Polymer</a>を使っているのだけど、1.xから<a href="https://www.polymer-project.org/2.0/docs/about_20">2.0</a> Previewに上げた。その時にやったことやはまったことなど。</p>
<h2 id="section">目次</h2>
<ol>
<li><a href="#heading-2016-12-11-assumption">前提</a>
<ol>
<li><a href="#heading-2016-12-11-references">参考ドキュメント</a></li>
</ol>
</li>
<li><a href="#heading-2016-12-11-upgrading-libraries">ライブラリーのアップグレード</a>
<ol>
<li><a href="#heading-2016-12-11-upgrading-paper-elements">Paper Elements</a></li>
</ol>
</li>
<li><a href="#heading-2016-12-11-modifying-existing-code">コードの修正</a>
<ol>
<li><a href="#heading-2016-12-11-changing-file-webcomponentsjs">webcomponentsjsなど</a></li>
<li><a href="#heading-2016-12-11-upgrading-paper-header-panel">paper-header-panel</a></li>
<li><a href="#heading-2016-12-11-removing-is-from-dom-module">dom-module</a></li>
<li><a href="#heading-2016-12-11-styling-dynamic-inserted-elements">動的に挿入される要素のスタイリング</a></li>
<li><a href="#heading-2016-12-11-fixing-paper-card-paper-button-error">paper-cardとpaper-buttonのエラー</a></li>
</ol>
</li>
</ol>
<h2 id="heading-2016-12-11-assumption">前提</h2>
<p>まず、Polymerを使っていると言うけど、<a href="http://webcomponents.org/">Webコンポーネント</a>用ライブラリー(フレームワーク)としての他、Googleが提唱するUIデザインであるマテリアルデザイン用のコンポーネント(<a href="https://elements.polymer-project.org/browse?package=paper-elements">Paper Elements</a>)なども使っていた。そのうち<a href="https://elements.polymer-project.org/elements/paper-card">Paper Card</a>ではまったりしたのでまずここで断っておく。</p>
<p>また、自分で作ったコンポーネントは一つだけで、フッターに置いてある検索ボックス用の<code>blog-search</code>という要素のみ。これの書き換えについても触れる。</p>
<h3 id="heading-2016-12-11-references">参考ドキュメント</h3>
<dl>
<dt><a href="https://www.polymer-project.org/2.0/docs/about_20">https://www.polymer-project.org/2.0/docs/about_20</a></dt>
<dd>Polymer 2.0になって変わったことの概要。</dd>
<dt><a href="https://www.polymer-project.org/2.0/docs/upgrade">https://www.polymer-project.org/2.0/docs/upgrade</a></dt>
<dd>Polymer 1.xから2.0へアップグレードする時のガイド。</dd>
<dt><a href="https://codelabs.developers.google.com/codelabs/polymer-2-carousel/">https://codelabs.developers.google.com/codelabs/polymer-2-carousel/</a></dt>
<dd>Polymer 2.0でコンポーネントを作るチュートリアル。</dd>
</dl>
<h2 id="heading-2016-12-11-upgrading-libraries">ライブラリーのアップグレード</h2>
<p><a href="https://github.com/KitaitiMakoto/apehuci/commit/8bd0c722cdeea984948ee4ebc89f5a3dfd5c74ca">https://github.com/KitaitiMakoto/apehuci/commit/8bd0c722cdeea984948ee4ebc89f5a3dfd5c74ca</a></p>
<h3 id="heading-2016-12-11-upgrading-paper-elements">Paper Elements</h3>
<p><a href="https://www.polymer-project.org/2.0/docs/about_20#installing">概要ページのインストールの項目</a>にあるように、Polymerは</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bower <span class="nb">install</span> <span class="nt">--save</span> Polymer/polymer#2.0-preview
</code></pre></div></div>
<p>でアップグレードできる。
この時に選択肢が出てくるが、Polymerは2.0、webcomponentsjsは1.0を選んでおけばよい。</p>
<p>各コンポーネントをインストールするにも同様に、各コンポーネントの<code>2.0-preview</code>ブランチをインストールすればいい。これまでは<code>paper-elements</code>という全体をまとめたコンポーネントがあって、それをインストールするだけで全Paper Elementがインストールできたのだけど、なぜか<code>2.0-preview</code>ブランチはないので、自分が使っている物を個別にインストール必要があった。めんどくさい(イシューは上がってる、と思ったのだけど、今探したら無いな、幻か知ら)。</p>
<p>あと、</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bower <span class="nb">install</span> <span class="nt">--save</span> paper-input#2.0-preview
</code></pre></div></div>
<p>みたいに、既存の物を<code>2.0-preview</code>にアップグレードすればいいコンポーネントと、</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bower <span class="nb">install</span> <span class="nt">--save</span> PaperElements/paper-card#2.0-preview
</code></pre></div></div>
<p>みたいに、GitHubのオーガニゼーションも明示しないといけないコンポーネントがある。<a href="https://github.com/Polymer/paper-card">Polymer/paper-card</a>を見てみて、<code>2.0-preview</code>ブランチがなければ<a href="https://github.com/PolymerElements/paper-card">PolymerElements/paper-card</a>を見る、ということをしなくてはならず、これもめんどくさい。</p>
<h3 id="heading-2016-12-11-changing-file-webcomponentsjs">webcomponentsjsなど</h3>
<p>webcomponentsjsなどは、パッケージその物はPolymerアップグレードの時に一緒に自動でアップグレードされるのだけど、Polymerが使うファイルが変わっていることがある。例えば<code>webcomponents.min.js</code>というファイルを使っていたのが<code>webcomponents-lite.js</code>に変わっていたりするので、コンソールのエラーメッセージを見ながら参照先を変えていく。</p>
<h2 id="heading-2016-12-11-modifying-existing-code">コードの修正</h2>
<p>アップグレードに伴って、いくつか既存のコードを修正する必要があった。大体は<a href="https://www.polymer-project.org/2.0/docs/upgrade">アップグレードガイド</a>に従えばいいが、幾つか嵌った所。</p>
<h3 id="heading-2016-12-11-upgrading-paper-header-panel">paper-header-panel</h3>
<p>コンポーネントをアップグレードしたら画面が真っ白になってしまった。</p>
<p>この日記の大部分は<a href="https://elements.polymer-project.org/elements/paper-header-panel">paper-header-panel</a>の中に入っているのだけど、これの使い方が変わっていて、そのせいだった。子要素を配置するための<a href="https://www.polymer-project.org/2.0/docs/upgrade#replace-content-elements">slot</a>(従来はcontent)に<code>name</code>属性で名前が付くようになって、ユーザー(=僕)が配置する子要素にも、対応する名前を付けておかなければいけなくなっていた。一応、<code>paper-header-panel</code>のソースコードコメントにはそのことが書いてある: <a href="https://github.com/PolymerElements/paper-header-panel/blob/226e265f151dfd229c68780626342dd2b6295f6f/paper-header-panel.html#L32-L37">paper-header-panel.html#L32-L37</a></p>
<p>これに合わせてテンプレートを修正した(<code>slot</code>属性を足しているのに注目):</p>
<p>Before:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><paper-header-panel</span> <span class="na">mode=</span><span class="s">waterfall-tall</span><span class="nt">></span>
<span class="nt"><paper-toolbar></span>
<span class="c"><!-- ... --></span>
<span class="nt"></paper-toolbar></span>
<span class="nt"><main></span>
<span class="c"><!-- ... --></span>
<span class="nt"></main></span>
<span class="nt"><footer></span>
<span class="c"><!-- ... --></span>
<span class="nt"></footer></span>
<span class="nt"></paper-header-panel></span>
</code></pre></div></div>
<p>After:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><paper-header-panel</span> <span class="na">mode=</span><span class="s">waterfall-tall</span><span class="nt">></span>
<span class="nt"><paper-toolbar</span> <span class="na">slot=</span><span class="s">header</span><span class="nt">></span>
<span class="c"><!-- ... --></span>
<span class="nt"></paper-toolbar></span>
<span class="nt"><main</span> <span class="na">slot=</span><span class="s">content</span><span class="nt">></span>
<span class="c"><!-- ... --></span>
<span class="nt"></main></span>
<span class="nt"><footer</span> <span class="na">slot=</span><span class="s">content</span><span class="nt">></span>
<span class="c"><!-- ... --></span>
<span class="nt"></footer></span>
<span class="nt"></paper-header-panel></span>
</code></pre></div></div>
<h3 id="heading-2016-12-11-removing-is-from-dom-module">dom-module</h3>
<p>カスタム要素を定義するのに使う<a href="https://www.polymer-project.org/1.0/docs/devguide/local-dom#template-stamping">dom-module</a>という要素(メタ要素?)をPolymerは提供している。カスタム要素の定義は本来JavaScriptでやるのだけど、<code>dom-module</code>を使うとHTMLを使ってある程度宣言的にできて、僕はこのアプローチを気に入っている。</p>
<p>自分で作ったカスタム要素である<code>blog-search</code>はこの<code>dom-module</code>で定義しているのだけど、その時に、<code>dom-module</code>に<code>is</code>属性を付けていた。</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dom-module</span> <span class="na">is=</span><span class="s">blog-search</span> <span class="na">id=</span><span class="s">blog-search</span><span class="nt">></span>
<span class="c"><!-- ... --></span>
<span class="nt"></dom-module></span>
</code></pre></div></div>
<p><code>is</code>というのは、その要素(ここでは<code>dom-module</code>)を更に拡張したバージョンの要素を使う、という時に使う物で、例えばオートコンプリート機能を追加した検索インプットを作って</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><input</span> <span class="na">type=</span><span class="s">search</span> <span class="na">is=</span><span class="s">auto-complete</span><span class="nt">></span>
</code></pre></div></div>
<p>などとして使う(ちなみにブラウザーベンダー間で要不要の意見が割れているらしいので、この仕様はなくなるかも)。</p>
<p><code>dom-module</code>を使う時には<code>is</code>属性は不要なので、ここでは使い方が間違っているのだけど、問題なく動いていた。単に無視されていたのだろうと思う。2.0 Previewに上げても同様に動いていたのだけど、Chromeで全く動かない(<code>blog-search</code>の定義自体が失敗している)ことに気が付いた。<code>is</code>を外すと動いたので、ChromeがネイティブでWebコンポーネントに対応しているのと関係していそうだが調べていない(他のブラウザーでは、現在のところ、Webコンポーネントの多くの機能がpolyfillやshimで動いている)。</p>
<h4 id="section-1">追記</h4>
<p><code>is</code>は、Polymer 1.xの頃には必要(正確には<code>name</code>または<code>is</code>が必要)だったので、<code>is</code>を使っていたのは正しかった。単に、2.0にする時に外し、<code>id</code>を付与する必要がるということだった。</p>
<h3 id="heading-2016-12-11-styling-dynamic-inserted-elements">動的に挿入される要素のスタイリング</h3>
<p>この日記の検索機能では、<code>XMLHttpRequest</code>で<a href="http://groonga.org/ja/docs/reference/executables/groonga-httpd.html">groonga-httpd</a> (NginxのGroongaモジュール)から検索結果を取得し、DOMツリー内に挿入している(参考:<a href="../02/07.html">日記に検索機能をつけた</a>)。Groongaが検索キーワードに<code>keyword</code>というクラスを付けてくれるので、赤い太字になるようスタイリングしていた。</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.keyword</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span>
<span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bolder</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Chromeでスタイリングされなくなった。これは、ChromeがShadow DOMを実装していて、Polymerもそのネイティブ実装を活かしている、つまり外部でのスタイル宣言がShadow DOM内に影響を及ぼしていないからだと思う。Webコンポーネントの大きな特長がこのカプセル化なのでむしろ歓迎すべき変更。というわけで、喜んで、Shadow DOMに閉じたスタイリングに変更した。</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dom-module</span> <span class="na">id=</span><span class="s">blog-search</span><span class="nt">></span>
<span class="nt"><template></span>
<span class="nt"><style></span>
<span class="c">/* ... */</span>
<span class="nc">.keyword</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--keyword-color</span><span class="p">,</span> <span class="no">red</span><span class="p">);</span>
<span class="nl">font-weight</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--keyword-font-weight</span><span class="p">,</span> <span class="nb">bolder</span><span class="p">);</span>
<span class="p">}</span>
<span class="nt"></style></span>
<span class="nt"></template></span>
<span class="c"><!-- ... --></span>
<span class="nt"></dom-module></span>
</code></pre></div></div>
<p>すると、今度はFirefoxでスタイルが外れてしまった。</p>
<p>FirefoxではShadow DOMが実装されておらずshimを使っている。具体的にはHTMLの<code>style</code>要素に、Shadow DOM内に閉じているのとある程度同等のスタイル宣言をして、それを持ってスコープ付きのスタイリングとしている。</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="c"><!-- ... --></span>
<span class="nt"><style></span>
<span class="c">/* ... */</span>
<span class="nc">.keyword.blog-search</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--keyword-color</span><span class="p">,</span> <span class="no">red</span><span class="p">);</span>
<span class="nl">font-weight</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--keyword-font-weight</span><span class="p">,</span> <span class="nb">bolder</span><span class="p">);</span>
<span class="p">}</span>
<span class="nt"></style></span>
<span class="c"><!-- ... --></span>
</code></pre></div></div>
<p>このスタイル宣言を活かすために行われるのが、「要素名と同じクラスを追加する」という処理だ。</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><blog-search></span>
<span class="c"><!-- ... --></span>
<span class="nt"><paper-input</span> <span class="err">...</span> <span class="na">class=</span><span class="s">"style-scope blog-search"</span> <span class="err">...</span><span class="nt">></span>
<span class="c"><!-- ... --></span>
<span class="nt"></paper-input></span>
<span class="c"><!-- ... --></span>
<span class="nt"></blog-search></span>
</code></pre></div></div>
<p>(<code>class</code>に<code>blog-search</code>が追加されている)</p>
<p>元々HTML内に書かれているタグであれば、このようにPolymerが自動でクラスを追加してくれるのだが、検索結果のように、動的に挿入される要素ではそうはいかない。仕方がないので、挿入する時に自分でクラスを追加するようにした。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ...</span>
<span class="nx">snippetHtml</span><span class="p">:</span> <span class="nx">article</span><span class="p">[</span><span class="mi">3</span><span class="p">].</span><span class="nf">join</span><span class="p">(</span><span class="dl">"</span><span class="s2"><br></span><span class="dl">"</span><span class="p">).</span>
<span class="nf">replace</span><span class="p">(</span><span class="dl">'</span><span class="s1"><span class="keyword"></span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1"><span class="keyword style-scope blog-search"></span><span class="dl">'</span><span class="p">),</span>
<span class="c1">// ...</span>
</code></pre></div></div>
<p>文字列処理なので乱暴だが、この場合はセキュリティホールにはならない(はず)。</p>
<p>「Firefoxはカスタムプロパティには対応しているんだからそっち使ってくれればいいんだけどなあ」とちょっと釈然としないが、まあ、過渡期ということで仕方ないのだろう。</p>
<h3 id="heading-2016-12-11-fixing-paper-card-paper-button-error">paper-cardとpaper-buttonのエラー</h3>
<p><code>paper-card</code>と<code>paper-button</code>が(例によって)Chromeでだけ動かない。</p>
<blockquote>
<p>Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes</p>
</blockquote>
<p>というエラーが出てしまっている。</p>
<p>ググるとStack Overflowがヒットして(<a href="http://stackoverflow.com/questions/40181683/failed-to-execute-createelement-on-document-the-result-must-not-have-childr">Failed to execute 'createElement' on 'Document': The result must not have children</a>)、見ると<code>created</code>(新しくは<code>constructor</code>)コールバックの使い方が、新しいCustom Elements仕様としては不正なようだ。しようがないのでフォークしてここを<code>ready</code>コールバックに書き換えて対応とした。イシューも上げた(<a href="https://github.com/PolymerElements/paper-card/issues/90">2.0-preview throw an error Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes #90</a>)。</p>
<p><code>ready</code>はCustom Elements標準にはなく、Polymer独自のコールバック。1.xの時代から存在していて、2.0でも残るようなので(<a href="https://www.polymer-project.org/2.0/docs/about_20#lifecycle-changes">Lifecycle changes</a>)、<code>blog-search</code>でも使っていたがそのままにしてある。</p>
<h4 id="section-2">追記</h4>
<p>これは<code>paper-card</code>や<code>paper-button</code>ではなく、<a href="https://github.com/Polymer/vulcanize">vulcanize</a>の問題だということが分かった(<a href="https://github.com/PolymerElements/paper-card/issues/90">PolymerElements/paper-card/issues/90</a>)。どうもJavaScriptのclass構文で定義した物ををvulcanizeがうまく扱えないということみたい。なので2.0でclass構文使う際には注意されたい。</p>
<hr />
<p>こんなところかな。あとは公式の<a href="https://www.polymer-project.org/2.0/docs/upgrade">アップグレードガイド</a>に従えばいいことばかりだった。</p>
<p>コミットログを見るとコードレベルで何をやっているかが分かると思う:
<a href="https://github.com/KitaitiMakoto/apehuci/commits/master">https://github.com/KitaitiMakoto/apehuci/commits/master</a></p>
https://diary.kitaitimakoto.net/2016/12/11.html
Polymerを2.0 Previewに上げた
2016-12-11T00:00:00Z
2016-12-11T00:00:00Z
<p>これはアドベントカレンダー「<a href="http://www.adventar.org/calendars/1885">年末年始おすすめ作品 BY.CORK Advent Calendar 2016</a>」の二日目です。</p>
<p>本当は二日目の僕が書く必要はないと思うのですが、初日がまさかのツイッター投稿(<a href="https://twitter.com/boogie_go/status/804517137784008704">https://twitter.com/boogie_go/status/804517137784008704</a>)だったので、「アドベントカレンダーとは何か」ということも一緒にご説明しますね。</p>
<h2 id="section">アドベントカレンダーとは</h2>
<p>アドベントカレンダーは、元々は、クリスマスまでの日数を数える日めくり(?)カレンダーだそうです(参考:<a href="https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%89%E3%83%99%E3%83%B3%E3%83%88%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC">ウィキペディア</a>)。</p>
<p>ただ、ここでのアドベントカレンダーは日本の(主にウェブ・スマホアプリの)エンジニアの慣習を指していて、「12月1日からスタートし、クリスマスまで、ある話題を決めて、それについて持ち回りでブログ記事などを書いていく」という物です。一箇所に色んなサービスや会社の知見が集まるので読む側としては毎日楽しみだし、後から見ても、一通り情報が手に入るので便利です。</p>
<p>これを、僕が所属する<a href="https://corkagency.com/">コルク</a>で、(技術情報でなく)おすすめ作品でやろう、ということになりました。経緯を把握していないんだけど、たぶん、「コルクはクリエイターのエージェンシーだが、ITにも力を入れている(入れていきたい)」というところからIT業界のノウハウとか取り入れようとしているので、そこからの派生なのかな。内輪褒めになっちゃうけど、みんな僕よりもずっと色々な物を見ているし、さすが編集担当なんかはそれを言語化するのもうまいので、僕自身が楽しみ。</p>
<p>僕の担当分「おすすめファンタジーまんが五選」は次の通り。</p>
<h2 id="section-1">諸星大二郎「妖怪ハンター」シリーズ</h2>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E5%A6%96%E6%80%AA%E3%83%8F%E3%83%B3%E3%82%BF%E3%83%BC-%E5%9C%B0%E3%81%AE%E5%B7%BB-%E9%9B%86%E8%8B%B1%E7%A4%BE%E6%96%87%E5%BA%AB-%E8%AB%B8%E6%98%9F-%E5%A4%A7%E4%BA%8C%E9%83%8E/dp/4086183900%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4086183900" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51TmXXBItiL._SL160_.jpg" width="104" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E5%A6%96%E6%80%AA%E3%83%8F%E3%83%B3%E3%82%BF%E3%83%BC-%E5%9C%B0%E3%81%AE%E5%B7%BB-%E9%9B%86%E8%8B%B1%E7%A4%BE%E6%96%87%E5%BA%AB-%E8%AB%B8%E6%98%9F-%E5%A4%A7%E4%BA%8C%E9%83%8E/dp/4086183900%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4086183900" target="_blank">妖怪ハンター 地の巻 (集英社文庫)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E8%AB%B8%E6%98%9F%E5%A4%A7%E4%BA%8C%E9%83%8E" target="_blank">諸星大二郎</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">集英社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2005-11-18</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4086183900" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>考古学者とされる稗田礼二郎が、フィールドワークで訪れた先々で、色々な怪異を体験するという話。シリーズ名に「ハンター」が含まれていますが、特にハントはしません。</p>
<p>怪異の元となっているのが宗教。舞台が日本ではあるのですが、特に土地土地の信仰を重視しているところが面白い。「神道はこういう宗教で、こういう神様がいて……」「仏教のこの宗派はこういう教えなので……」みたいなことは焦点ではなく、その上で、「この土地では古来の巨石への信仰と仏教がこう結び付いて……」など「生きた信仰」を描き出し、その可視化として、化け物が出てきます。(そして倒さず、去るのを待ちます……。)</p>
<p>それまでは大体ライトファンタジー、それも異世界ファンタジーばかり読んでいたので、ファンタジー観が変わりました。と同時に、宗教や信仰というのは、本来(?)小さなコミュニティの物であって、現代でも適応して形を変えていく物なんだ、と、世界観まで変わる体験でした。諸星大二郎を読んでから、神社を巡るのが楽しくなりました。</p>
<h2 id="section-2">五十嵐大介『はなしっぱなし』</h2>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E3%81%AF%E3%81%AA%E3%81%97%E3%81%A3%E3%81%B1%E3%81%AA%E3%81%97-%E4%B8%8A-%E4%B9%9D%E9%BE%8DCOMICS-%E4%BA%94%E5%8D%81%E5%B5%90-%E5%A4%A7%E4%BB%8B/dp/4309728405%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4309728405" target="_blank"><img src="http://ecx.images-amazon.com/images/I/419DCGKFDZL._SL160_.jpg" width="104" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E3%81%AF%E3%81%AA%E3%81%97%E3%81%A3%E3%81%B1%E3%81%AA%E3%81%97-%E4%B8%8A-%E4%B9%9D%E9%BE%8DCOMICS-%E4%BA%94%E5%8D%81%E5%B5%90-%E5%A4%A7%E4%BB%8B/dp/4309728405%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4309728405" target="_blank">はなしっぱなし 上 (九龍COMICS)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E4%BA%94%E5%8D%81%E5%B5%90%E5%A4%A7%E4%BB%8B" target="_blank">五十嵐大介</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">河出書房新社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2004-02-18</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4309728405" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>これも現代の日本を舞台にしたファンタジーですが(いわゆる現代ファンタジーとは違う)、よりライトと言うか、「事件」ではなくて、日常的な「気が付かないけれど起こっていること」というのを描いています。話に山や谷があまりないのですが、そのファンタジー世界に浸ることができる短編集です。</p>
<p>五十嵐大介は、読むと感性の一部を譲り受けることができるような作家で、読んだ後しばらくは日常の見方が変わる、本当に彼が見ているように見える気分になるようなパワーを持った人だと思います。理屈先行の僕はその減退が割と早いので、一時期は毎日『はなしっぱなし』を読んでファンタジー的感覚を補充していました。</p>
<h2 id="section-3">ニール ゲイマン「サンドマン」シリーズ</h2>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E3%82%B5%E3%83%B3%E3%83%89%E3%83%9E%E3%83%B3-1-DC-COMICS-VERTIGO/dp/4924914061%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4924914061" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51KMRRKBRYL._SL160_.jpg" width="97" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E3%82%B5%E3%83%B3%E3%83%89%E3%83%9E%E3%83%B3-1-DC-COMICS-VERTIGO/dp/4924914061%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4924914061" target="_blank">サンドマン (1) (DC COMICS VERTIGO)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E3%83%8B%E3%83%BC%E3%83%AB%E3%83%BB%E3%82%B2%E3%82%A4%E3%83%9E%E3%83%B3" target="_blank">ニール・ゲイマン</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">インターブックス</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 1998-04-08</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4924914061" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>どう紹介したものか……。</p>
<p>主人公は「<a href="https://ja.wikipedia.org/wiki/%E3%82%B6%E3%83%B3%E3%83%88%E3%83%9E%E3%83%B3">サンドマン</a>」「モルフェウス」などの呼び名を持つ超常の存在ドリーム。文字通り夢(そして眠り)を司る存在で、宇宙の始まりから存在し、人間が滅んでも(夢を見る知性体がいる限り)あり続ける存在です(同様の存在が他にも何人がいて、その一つ、ドリームの姉であるデス(もちろん、死を司る)とは仲がいいのが読んでいて感じられます)。例えば、物語の冒頭で、魔術師に彼が捕えられてしまうのですが、そうすると世界の眠りがめちゃくちゃになり、ある人は永久に夢を見続けることに、また別の人は眠ることができなくなったりしてしまうほどで、本当に夢その物なのです。</p>
<p>まんがはその彼のアドベンチャーを描いた物。始め二巻は捕えられてから出るまでと、捉えられている間に奪われてしまった三つのアイテムを見付け出すまで。次の二巻は、「渦」と呼ぶ、夢に関するおかしな現象が、アメリカのある少女を中心に(というか、彼女が渦その物となって)国中を巻き込む規模で起こり、その解決まで。第五巻は完全に独立した短編集で、猫が夢を見る話などがある。そして、六巻以降は、残念ながら邦訳されていない。</p>
<p>上手く感想を言葉にできないのだけど、伏線の扱いが上手とかも技術的な点もありますが、とにかくその発想と、夢に対する解釈の深さに圧倒されっぱなしのまんがでした。</p>
<h2 id="section-4">萱島雄太『西遊少女』</h2>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://p.booklog.jp/book/54478" target="_blank"><img src="http://img.p.booklog.jp/393D6F56-DC31-11E1-B1E1-2928058D85C2_m.gif" width="105" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://p.booklog.jp/book/54478" target="_blank">西遊少女 #01 -無料サンプル号-</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E8%90%B1%E5%B3%B6%E9%9B%84%E5%A4%AA" target="_blank">萱島雄太</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">パブー</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2012-08-02</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/3/54478" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>[disclosure]僕はかつて、『西遊少女』が連載されていたパブーの開発をしていました。</p>
<p>有名な『西遊記』をモチーフに、少女になった三蔵、沙悟浄、八戒、悟空が天竺を目指す……向かう話。主人公の三蔵はそもそも天竺に行きたくなくて、何とか離脱しようとするところを他の連中が頑張って連れ戻すスラップスティックです。<a href="http://p.booklog.jp/">パブー</a>では非公開になっており、<a href="http://mavo.takekuma.jp/title.php?title=67">電脳マヴォ</a>で公開されています。</p>
<p>当時(2012年)としては非常に珍しい、ウェブの縦スクロールを意識した、というか、その特徴を使ってのびのびと遊んでいるまんが。</p>
<p>単に「スクロールを活かす」というのだと「あーはいはい『実験的』まんがね、はいはい」となりがちだと思うんですが、『西遊少女』はお話自体が面白いので、そういう「技術に偏った奴らが何かやってる」というネガティブイメージを生まないという意味でもおすすめです。パブーの頃は第九話で連載がストップしてしまったのですが、マヴォでは最後まで描かれるんだろうか……。</p>
<h2 id="section-5">曽田正人『テンプリズム』</h2>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://www.amazon.co.jp/%E3%83%86%E3%83%B3%E3%83%97%E3%83%AA%E3%82%BA%E3%83%A0-1-%E3%83%93%E3%83%83%E3%82%B0%E3%82%B3%E3%83%9F%E3%83%83%E3%82%AF%E3%82%B9-%E6%9B%BD%E7%94%B0-%E6%AD%A3%E4%BA%BA/dp/4091864104%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4091864104" target="_blank"><img src="http://ecx.images-amazon.com/images/I/61u7E-vwJHL._SL160_.jpg" width="105" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://www.amazon.co.jp/%E3%83%86%E3%83%B3%E3%83%97%E3%83%AA%E3%82%BA%E3%83%A0-1-%E3%83%93%E3%83%83%E3%82%B0%E3%82%B3%E3%83%9F%E3%83%83%E3%82%AF%E3%82%B9-%E6%9B%BD%E7%94%B0-%E6%AD%A3%E4%BA%BA/dp/4091864104%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4091864104" target="_blank">テンプリズム 1 (ビッグコミックス)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E6%9B%BD%E7%94%B0%E6%AD%A3%E4%BA%BA" target="_blank">曽田正人</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">小学館</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2014-08-29</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4091864104" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>[disclosure]僕は現在、『テンプリズム』の編集を担当しているコルクに所属しています。</p>
<p>曽田正人初めての異世界ファンタジー。伝説の力を有するツナシが、魔力と呼ばれる力を持ち、どんどん国を拡大して世界を侵略していっている<ruby>骨<rt>グゥ</rt></ruby>の国と戦う物語。</p>
<p>曽田正人と言えば僕は『昴』から入り、それ以外の作品も読んでいった、という流れなのですが、どれも共通するのは、主人公が天才であること。特に、自分の力で成長することのできる天才。ファンタジーになってもそれは健在なのですが、変わったのが天才性が目に見えるようになったことです。</p>
<p>作中で「オロメテオールの力」と呼ばれるその力はツナシの眼に宿り、自分の意図ではその発動を制御できない。思わぬ時にその力が使えたり、望んだ時に使えなかったりする。こう書くと分かりやすいですが、結構、これまでの主人公の天才性と似ていますね。そして、ここが、さすが曽田正人という感じなんですが、「目に見える」「名前がある」という、「自分の外にある物である」と認識する条件が満たされているので、天才 vs 天才性という軸で主人公を眺める楽しみも出てきています。その上で、「それでも自分なのだし、責任を引き受ける」という決断を描くまでの成長があるところが、ファンタジーにして生まれた曽田正人の新しさだと感じました。</p>
https://diary.kitaitimakoto.net/2016/12/02.html
アドカレ #コルクおすすめ2016 二日目 おすすめファンタジーまんが五選
2016-12-02T00:00:00Z
2016-12-02T00:00:00Z
<p><a href="https://jxck.io/">Jxck</a>さんによる<a href="https://blog.jxck.io/entries/2016-06-25/intersection-observer.html">Intersection Observer を用いた要素出現検出の最適化</a>という記事を読んで、Intersection Observerを使ってみたくなった。そこで、これを使ってモバイル用のスクロールスパイを実装してみた:<a href="https://kitaitimakoto.github.io/scrollspy-example/">Scrollspy example</a>(iOS以外のFirefoxが一番綺麗に動く。次点でSafari。)</p>
<h2 id="intersection-observer">Intersection Observer</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Intersection Observer</a>を使うと<code>scroll</code>イベントの監視が不要になるケースがあり、その際に動作が軽快になるというのが主なメリットで、ユースケースとしてはデータや(画像などの)リソースの遅延読み込みが想定されているようだ。<code>img</code>要素が画面に入ってくる直前のタイミングで画像読み込みを開始する、という用途だ。</p>
<p>ところが<code>scroll</code>イベントの出番は他にもあり、最近出くわしたのが画面上部固定のナビゲーションとスクロールスパイだった。</p>
<h2 id="section">固定ナビゲーション</h2>
<p>固定のナビゲーションというのは、 この言い方だったら「<code>position: fixed</code>使えば?」という感じだが、</p>
<ul>
<li>最初はナビゲーションが普通にスクロールで移動するようになっている</li>
<li>スクロールによってナビゲーションが画面最上部に来たら、その位置で固定する</li>
</ul>
<p>という物で、「ナビゲーションが画面最上部に到達しているかどうか」を判定するのに、<code>scroll</code>イベントを監視しなくてはならない。これについてはIntersection Observerではなく、<a href="https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md">Passive Event Listener</a>の出番なのだが(これもJxckさんの記事<a href="https://blog.jxck.io/entries/2016-06-09/passive-event-listeners.html">Passive Event Listeners によるスクロールの改善</a>が素晴らしい)、今回はブラウザーを絞って、CSSの<code>position: sticky</code>で解決した(参考:<a href="http://unformedbuilding.com/articles/css-position-sticky/">素敵な position: sticky; Unformed Building</a>)。</p>
<h2 id="section-1">スクロールスパイ</h2>
<p>残ったスクロールスパイを、Intersection Observerで実装してみた。それもモバイル用にしてみた。</p>
<p>別にモバイルに絞らなくても、Intersection Observerによるスクロールスパイの実装というのは妥当な選択ではあるのだが、別所で「モバイルでもスクロールスパイのようなナビゲーションが欲しい」という話をしていたこともあって、その解決策の例示と併せた。</p>
<p>モバイルでスクロールスパイがあまり見られないのは、やはり「常時サイドバーなどでナビゲーションを表示する」というのが、画面の狭い環境では受け入れ難いから。ただ、一般にヘッダーとフッターくらいは常時表示が許容されているので、ここをスクロールスパイに使ってしまおうというのがアイディアだった。スクロールスパイの目的は</p>
<ul>
<li>全体のナビゲーションを表示すること</li>
<li>その中で現在位置を示すこと</li>
</ul>
<p>だが、後者に特にフォーカスして、「現在位置が変わる度に表示を切り替えて、その場所の名前にする」という方法をとった。前者については、タップするとナビゲーションの全メニューを表示することで解決とした。</p>
<p>あと、巷のスクロールスパイでは、現在位置が変わった時に</p>
<ol>
<li>一旦ナビゲーション中の全部を「選択されていない」状態にする</li>
<li>その後、現在位置に対応するメニューだけを「選択」状態にする</li>
</ol>
<p>となっているのが多いのだが、Intersection Observerによって注目すべき対象が絞れるので、不要なループを削れたのも嬉しい。</p>
<h2 id="firefoxcss">FirefoxのCSS対応</h2>
<p>スムーススクロールやカスタムプロパティ、<code>position: sticky;</code>、<code>text-decoration-style</code>など、Firefoxが、意外と色々なCSSの機能に対応していて驚いた。</p>
<p>特にスムーススクロールはお気に入りで、JavaScriptでスムーススクロールを実装する場合には、自動で<code>location</code>のフラグメント部が変わらないし、何より<code>:target</code>擬似クラスの対象が変わらない。その点、ブラウザーネイティブのスムーススクロールなら<code>:target</code>を使ったスタイリングもできるので非常にいい(今回は使わなかったが)。</p>
<p>最後に、改めてリンクを:</p>
<ul>
<li><a href="https://kitaitimakoto.github.io/scrollspy-example/">Scrollspy example</a></li>
<li><a href="https://github.com/KitaitiMakoto/scrollspy-example">KitaitiMakoto/scrollspy-example</a></li>
</ul>
https://diary.kitaitimakoto.net/2016/09/12.html
Intersection Observerによるモバイル用スクロールスパイ
2016-09-12T00:00:00Z
2016-09-12T00:00:00Z
<p>久し振りに<a href="https://groonga.doorkeeper.jp/events/48368">Groongaで学ぶ全文検索 2016-08-26</a>@<a href="http://www.eli-sys.jp/">イーライセンスシステムズ</a>に参加してきた。</p>
<p>今日は、全文検索がどういう物かよく分かっていないという人が複数人いたことから、全文検索の仕組みと、スコアリングについて話をしてもらった。</p>
<h2 id="section">全文検索の仕組み</h2>
<p>ホテルデータベースを考える。ホテルのある場所、種類(ホテルか旅館か)、泊数や日付、説明があって、それぞれで絞り込みができる。このうち、説明が全文検索(テキスト検索)の対象になる。</p>
<p>ある言葉(「軽井沢」)で検索する時に、全ホテルを順番に見て、それぞれに検索語が含まれているかを調べる方法は、非常に時間が掛かる。耐えられる時間内に検索結果が返ってこない。そこで、検索結果を速く返すための準備をすることになる。この準備をしたデータのことをインデックスと呼ぶ。</p>
<p>インデックスというのは、日本語では索引で、考え方は本の末尾にある索引と同じ。本の方の索引は、専門用語などの「言葉」と、その言葉が出てくる「ページのリスト」を並べた物だ。全文検索エンジンのインデックスも同様で、キーワードと、そのキーワードを含んでいる文書(のIDなど)のペアを並べた物になっている。検索語がこのインデックスに載っていると、検索するのが速い。但し、インデックスに載っていない検索語は、検索できなくなる。そこで、インデックスのキーに載っている単語を増やすのがコツになる。</p>
<p>元々の説明文などからこのキーを作る方法は二種類ある。一つは、説明を日本語として解釈して分割し、分割結果をキーにする方法。形態素解析と呼ばれる。</p>
<p>もう一つは単純に決めた文字数で区切ること。「軽井沢に行こう」という説明文だったら、「軽井」「井沢」「沢に」「に行」「行こ」「う」と区切ることができる。この時、「軽井沢」で検索すると、どこにもこれと同じキーはないのでヒットしない。そこで、検索語の方も同じように区切って「軽井」「井沢」とする。それから「説明文に『軽井』を含んでいるホテル一覧」と「『井沢』を含んでいるホテル一覧」を取得し、その共通部分を取ると、「軽井沢」を含むホテル情報が手に入る。正確には、「『軽井沢』を含む<strong>可能性のある</strong>ホテル情報一覧」が手に入る。「井沢さんと軽井さんが経営しています」という文書もヒットするからだ。それを防ぐため、共通部分を取る時に、「キーワードの順番と離れ具合を見て、この順番で隣り合っているような説明文のホテル一覧」を取得するようにする。すると「説明文に『軽井沢』を含んでいるホテル一覧」になる。</p>
<h2 id="section-1">スコアリング</h2>
<p>なぜスコアをつけるのか、という話を最初にした。それは、検索結果一覧の中のそれぞれで、ユーザーの欲しそう度合いが違うから。欲しそうな度合いの大きい物を先に提示して、そうでない物を後に提示すると、ユーザーが本当に欲しかった物をよりうまく提供できるだろうという考え方による。この欲しそう度合いを決める際に、エリアなどの項目をどれくらい重視するかというのを数値にした物のことを「重み」と呼んで、重みを決めることを「重み付け」と言う。検索スコアをつける際、それぞれの重みを考慮したスコアをつけることになる。</p>
<p>スコアリングの際には、確実さと重要さの二つを考えて行う。</p>
<p>確実さというのは、「軽井沢」で検索した時に、説明文で「軽井沢にほど近い」となっているホテルよりも、エリアがズバリ「軽井沢」となっている方が重くなるようにスコアをつけること。</p>
<p>重要さというのは、仮に説明文にタイトルと本文があった時に、タイトルの方が大事だろうと考えて、タイトルにヒットした場合により高いスコアをつけること。</p>
https://diary.kitaitimakoto.net/2016/08/26.html
全文検索とスコアリングの仕組み
2016-08-26T00:00:00Z
2016-08-26T00:00:00Z
<p>携帯を買い換えた。</p>
<p>これまでFirefox OSの<a href="http://au-fx.kddi.com/">Fx0</a>を使っていたが、諸々の事情で、と言うかタブレットのNexus 7が充電できなくなってしまって、さすがに携帯端末がFirefox OSしかないのは不便が大きすぎるのでAndroid携帯に変えた。本当はNexusにしたかったけど、auにはFirefox OSを出してもらった義理を(一方的に)感じているので、一回はauを選びたかった。その中で有効な選択肢はGalaxy S7 edgeとHTC 10だったが何となくでGalaxyにした。</p>
<p>正直Galayx S7 edgeもHTP 10も大き過ぎるので、多少は小さいHTC 10にしようかなあとは思っていたのだが、店頭で見てみるとどちらも変わらないのでGalaxyにしたのだった。</p>
<p>それはそれとして、タブレットをどうしようかすごい迷ってて、Nexus 7は最高だったと思う。Nexus 9は大き過ぎる。iPad miniは有力候補だけどちょっとした不便を解消したいと思って簡単なアプリ作ってもストア申請めんどいなあ、だったらAndroidの方がまだ自由かなあとか、ASUSのAndroidタブレット大きさ的にはいいけど最新のOSに追従してほしいなあとか、なんかいい感じのところがない。一月くらいGalaxyとくらしてみて決めようと思う。早速BookLive!アプリでまんが読んでみたところやっぱちょっと画面小さいので、何かは買うと思うが。</p>
https://diary.kitaitimakoto.net/2016/07/17.html
Farewell to Firefox OS
2016-07-17T00:00:00Z
2016-07-17T00:00:00Z
<p><a href="http://rouge.jneen.net/">Rouge</a>というRuby製のシンタックスハイライト用ライブラリーがあって、それのFluentd設定ファイル用の追加レクサー(トークナイザー)を作ってみた。</p>
<p><a href="https://github.com/KitaitiMakoto/rouge-lexers-fluentd">https://github.com/KitaitiMakoto/rouge-lexers-fluentd</a></p>
<p>始めはApache用のレクサーを使ってみたんだけどうまくいかなかった。<code><Directory></code>とかのキーワードがホワイトリスト形式だったり(これはサブクラス作ることで簡単に解決できたけど)、やっぱりFluentd独自の構文に対応したくなったりしてきたので、自分で書いたのだった。</p>
<p>使ってみると、こんな感じ。</p>
<div class="language-fluentd highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Receive events from 24224/tcp</span>
<span class="c"># This is used by log forwarding and the fluent-cat command</span>
<span class="p"><</span><span class="nl">source</span><span class="p">>
</span> <span class="nb">@type</span> <span class="ss">forward</span>
<span class="nc">port</span> <span class="ss">24224</span>
<span class="p"></</span><span class="nl">source</span><span class="p">>
</span>
<span class="c"># http://this.host:9880/myapp.access?json={"event":"data"}</span>
<span class="p"><</span><span class="nl">source</span><span class="p">>
</span> <span class="nb">@type</span> <span class="ss">http</span>
<span class="nc">port</span> <span class="ss">9880</span>
<span class="p"></</span><span class="nl">source</span><span class="p">>
</span>
<span class="c"># Match events tagged with "myapp.access" and</span>
<span class="c"># store them to /var/log/fluent/access.%Y-%m-%d</span>
<span class="c"># Of course, you can control how you partition your data</span>
<span class="c"># with the time_slice_format option.</span>
<span class="p"><</span><span class="nl">match</span><span class="sr"> myapp.access</span><span class="p">>
</span> <span class="nb">@type</span> <span class="ss">file</span>
<span class="nc">path</span> <span class="ss">/var/log/fluent/access</span>
<span class="p"></</span><span class="nl">match</span><span class="p">>
</span></code></pre></div></div>
<p>Rougeはターミナル向けにもフォーマットできる。
<img src="https://gyazo.com/89808bd1f93c33658cc632253980677f.png" alt="シンタックスハイライトのターミナルアウトプット" /></p>
<p>READMEに書いた通りまだやることは残っているけど、取り敢えず最低限の用はなすと思う。具体的には、『<a href="https://gihyo.jp/dp/ebook/2014/978-4-7741-6698-8">サーバ/インフラエンジニア養成読本 ログ収集〜可視化編</a>』のシンタックスハイライトをできる程度には使える。</p>
<p><img src="https://gyazo.com/9db066f8005d35bba48e0196917bf1ec.png" alt="サーバ/インフラエンジニア養成読本 ログ収集〜可視化編でのシンタックスハイライト" /></p>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E9%A4%8A%E6%88%90%E8%AA%AD%E6%9C%AC-%E3%83%AD%E3%82%B0%E5%8F%8E%E9%9B%86%7E%E5%8F%AF%E8%A6%96%E5%8C%96%E7%B7%A8-%E7%8F%BE%E5%A0%B4%E4%B8%BB%E5%B0%8E%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90%E7%92%B0%E5%A2%83%E3%82%92%E6%A7%8B%E7%AF%89-Software-Design/dp/4774169838%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774169838" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51lSb2Ie7WL._SL160_.jpg" width="105" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E9%A4%8A%E6%88%90%E8%AA%AD%E6%9C%AC-%E3%83%AD%E3%82%B0%E5%8F%8E%E9%9B%86%7E%E5%8F%AF%E8%A6%96%E5%8C%96%E7%B7%A8-%E7%8F%BE%E5%A0%B4%E4%B8%BB%E5%B0%8E%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90%E7%92%B0%E5%A2%83%E3%82%92%E6%A7%8B%E7%AF%89-Software-Design/dp/4774169838%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774169838" target="_blank">サーバ/インフラエンジニア養成読本 ログ収集~可視化編 [現場主導のデータ分析環境を構築!] (Software Design plus)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E9%88%B4%E6%9C%A8%E5%81%A5%E5%A4%AA" target="_blank">鈴木健太</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">技術評論社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2014-08-08</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4774169838" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
https://diary.kitaitimakoto.net/2016/06/11.html
Fluentd設定ファイルのシンタックスハイライト
2016-06-11T00:00:00Z
2016-06-11T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/43780">Groongaで学ぶ全文検索 2016-05-20</a>に行って来た。</p>
<p>今日の話題は「類似文書検索」。</p>
<p>参加者の一人が、今作っているサービスでMroonga(Groongaの機能をMySQL経由で使うストレージエンジン)を使っていて、その類似文書検索のパラメーター調整の相談があるということで、類似文書検索が話題に選ばれた。</p>
<p>まだ世に出ていないサービスなので、以降はその物ではなくて僕が適当に変換した話を書く。</p>
<h2 id="section">サービス概要(フェイク)</h2>
<p>話をブログに例えると、はてなダイアリー(はてなブログではない)のような物。</p>
<p>まず、普通にブログ記事を書ける。</p>
<p>そして、(最近の人は知らないかも知れないけど)「おとなり日記」という機能があって、自分の記事と似ている記事一覧が、コメントの所に表示される(今はないかも?)。</p>
<p>勿論、はてなダイアリーには他にも色々な機能があるが、今日の話に必要なのはこの二つ。</p>
<h2 id="section-1">やりたいことと直面している課題</h2>
<p>やりたいのは、日記を書いた時に、おとなり日記のように、似た記事を探してきて、見せること。</p>
<p>そこの所を、Mroongaの類似文書検索の機能を使って実装したのだが、ヒットする類似文書が少なすぎるので、もっと増やしたい。そのために、今はMroongaが「似ているといえば似ているが、この特徴を似ていると言ってしまうと、およそ全ての日記がお互いに似ていることになってしまう、つまり何も言っていないに等しい」と判断して切り捨てている特徴を、復活させたい、そのためのパラメーターがあるのであれば知りたい、ということだった。もう少し具体的に言うと、ほぼ全日記に含まれるような言葉は、検索時に省かれているが、それを復活させたいということ。</p>
<p>なお、そうするとノイズが増えることは理解していて、ただ、全体の件数が少ない今のフェイズに限っては復活させたいということだった。増えてきたら、そういう「どの日記にも含まれているキーワード」は検索語に含まないように調整するとのこと。</p>
<h2 id="groonga">Groongaの類似文書検索</h2>
<p>類似文書検索を知らない参加者もいたので、まずはその説明から行われた。</p>
<p>いつものように入力と出力を考えると、入力は(今書いたばかりの)日記で、出力はその日記と似ている日記。こうした入出力を実現するためにシステムはこうなっているとよい。というか、Groongaの類似文書検索はこうなっている。</p>
<h3 id="section-2">1. 入力の文書から特徴となる語(=特徴語)を抜き出す</h3>
<p>例えば「私はRubyができます。」という日記を書いたとする。この時、「は」とか「私」とかは、非常に多くの日記にも含まれる。「ぼくはPythonができます。」という日記を誰かが書いていたとしたら、そこにも「は」が含まれる。</p>
<p>一方で「Ruby」は(Rubyistのための開発日誌サービスでもない限り)あまり出てこない。同様に、別の人の日記の「Python」もあまりなさそう。こうした「他の文書には入っていないけど、この文書(を含む小数の文書)には入っている」という語が、この文書の特徴語になる。</p>
<h3 id="or">2. 特徴語でOR検索する</h3>
<p>さっきは違うと書いたけど、仮に、「私」と「Ruby」が、先の日記の特徴語だとしよう。似た日記を探す際には、まずその二つの語でOR検索する。すると、今日の日記の特徴語が含まれる記事がヒットする。これは、「特徴」が同じなので、似ていると言えるだろう。</p>
<p>これで類似文書が選べたことになる。</p>
<p>但し、特徴語が上手に選ばれていれば、だ。</p>
<h2 id="section-3">特徴語の妥当な抽出方法</h2>
<p>日記から特徴語を抜き出す時は、日本語として自然な単語やそれに類する物(≒形態素)を抜き出すのがよい。</p>
<p>何らかの語を抜き出す方法として、文字数を決めて、その文字数単位で切り出すことも出来る。例えば二文字と決めると、「ぼく」「くは」「はP」「Py」「yt」「th」「ho」「on」「nが」「がで」「でき」「きま」「ます」「す。」と14の語(?)を抽出できる。しかし、さっき言ったようにOR検索した時、「『がで』という文字列が含まれている日記はにている」という判断をされても、困ってしまう。</p>
<p>全文検索ではこうした区切り方(n-gram)もありなのだが、類似文書検索の場合は、形態素による分割一択になる。</p>
<h2 id="section-4">課題の見直し</h2>
<p>さて、ここで課題に戻ろう。</p>
<ol>
<li>おとなり日記の数が少なすぎる(のでサービス内回遊ができない)。</li>
<li>それは、特徴語を選ぶ時の基準が厳しくて、検索に使われる特徴語が少ないからのように思われる</li>
<li>なので、「私」や「は」といった通常使わないような語も特徴語として類似文書検索に使うようなパラメーター(など何らかの方法)を知りたい</li>
</ol>
<p>ということだった。結論は「そのようなパラメーターはない」だった(Mroongaには。Groongaにはある)。</p>
<p>だが、そもそも、課題は、そういうことではないのでは? という疑問が呈される。以下「再現率」と「適合率」という言葉を使う。勉強会ではこれらの言葉も説明もあったのだが、実はこの勉強会の2015年11月6日の回でそのことをやっていたので、ここでは端折る。以下の勉強会ページの下に過去の勉強会レポートがまとまっているので、該当日の所を探して読まれたい。</p>
<p><a href="https://groonga.doorkeeper.jp/events/43780">https://groonga.doorkeeper.jp/events/43780</a></p>
<p>課題を言い換えると「再現率を増やしたい」ということになる。しかし再現率と適合率はトレードオフの関係にあるので、再現率を増やすと適合率が下がる、つまりノイズが増えて、ユーザーが使わなくなりそうである。</p>
<p>ただ、そもそも、たくさんヒットした時に、全部見る人はいない(ような文脈が多い)。大体、最初の数件しか見ないので、ヒットする件数が多いか少ないかというよりも、どの日記が上に来ているのか、という方が重要になる。つまり、検索結果のスコアリングが大事だということだ。スコアリングはMroongaで色々と調整できるので、そこで頑張るのがよいということになる。</p>
<p>また、検索結果の中にユーザーが望む物がなかった時のフォローを入れておくというのも最近よくやられている(らしい)。例えば、今Googleで「ズートピア」を検索すると、ページ下部で次のように関連キーワードが案内される。
<img src="https://gyazo.com/0fd02873157e651e6fe240e7aad68bf3.png" alt="「ズートピア」検索結果画面の下部にある関連キーワード" /><br />
ページ下部まで見たということは望みの検索結果が得られなかったと判断して、「もしかしてこういう検索をしたかったのでは?」と提案しているわけだ。</p>
<p>ウェブページの全文検索ではなく、ツイートやチャットだと、新しい方が探したいことが多いだろうから、投稿日時を使ってスコアリングするというのもある。日記にお気に入り機能があれば、お気に入りが多い物のスコアを上げてもいい。</p>
<p>このように、文書その物以外のメタデータを使って、よりユーザーの望みそうな物に高い検索スコアを与え、上位に表示させるというのが最近のはやりらしい。</p>
https://diary.kitaitimakoto.net/2016/05/20.html
Groonga悩み相談「類似文書検索で検索結果が少ないからより多くヒットするようにパラメーター調整したい」
2016-05-20T00:00:00Z
2016-05-20T00:00:00Z
<p>本なんて、パンチ穴を開けた紙の束で売ればいいのではないだろうか。</p>
<p>みんな「マイバインダー」を持って、そこに本を挟むようにすれば、一冊という単位に縛られず、バインダーの厚さが許す限りは、何冊持ち歩いてもよい。仕切りのカードを挟んで、そこから後ろに、お気に入りの本のお気に入りのページを集めた物を常設しておいてもよい。小説のお気に入りの場面、まんがのコマ、詩、ビジネス格言集、参考書のテスト範囲……。</p>
<p>本屋では、本ごとの専用のバインダーを置いてそこで売る。買う時はレジに運んで、店員が中から紙の束だけ外し、客に渡す。当然、ファンは、タイトルや表紙絵、写真が入ったそのバインダーを欲しがるだろうから、マーチャンダイズとして売ればいい。まんがの原画やアニメのセル画のように、プレミアを付けて、プレゼントにしてもよい(そして転売される)。</p>
<p>バインダーの方も、専用デザイナーが生まれて気に入った物を買うようになったり、好きな写真家の写真入りにしたり、イラストレーターの一点物を大事にしていたり、iPhoneケースのように方々で作って売られるようになるだろう。</p>
<p>どうして、こういう形の売り方がされてこなかったのだろうか。</p>
https://diary.kitaitimakoto.net/2016/05/17.html
パンチの空いた本
2016-05-17T00:00:00Z
2016-05-17T00:00:00Z
<p>ウェブページにアノテーションを付ける時、ページの更新に対してアノテーションの指し示す場所はどうしたらいいのだろうという悩みがある。Robust Linksのやり方と同じにアノテーション作成日時を記録し、その時点でのウェブページをInternet Archive上のページなどとして参照しておけばいいのだと思い至った。そこから、欠けたピースはやはり「DOMの差分計算」になりそうだとも思った。</p>
<h2 id="section">アノテーション</h2>
<p>ウェブページのある部分を大事だと思ってハイライトしたり、そこに自分の疑問点や思い付きを書き込んでおきたいことがある。前者は栞として後で参照するのに使ったり、はてなスターのように(はてなスターを使ったことがないので推測だ)「いいことを書いてあると思う」ということを書いた人、そのページを読む人に伝えるために使ったりする。後者はあまり見ないかも知れないが、例えばはてなブックマークはページ全体に対するコメントと見做せるだろう。「<a href="../04/10.html">日記のコメント用にHypothes.isを埋め込んでみた</a>」に書いたようなまさにそのためのツールもあるにはある。ウェブページを離れれば、Kindleで日々やっていることとして想像しやすい人も多いと思う。</p>
<p>この二つを「アノテーション(注釈)」と定義して、その(プログラムで扱うための)表現方法ややり取りのためのプロトコルを、W3Cがワーキンググループを立ち上げて策定している(<a href="https://www.w3.org/annotation/">W3C Web Annotation Working Group</a>。<a href="https://twitter.com/kzakza">kzakza</a>さんの<a href="http://code.kzakza.com/2014/08/w3c-web-annotation-working-group/">W3C Web Annotation Working Group 紹介</a>も読まれたい)。これをWeb Annotationと呼ぶ(これ、とか言ったが、どこのことを呼ぶんだかはっきりとは考えていない)。策定中でまだ変わるだろうし、本題でもないので詳細は気にしないでいいのだが、JSON(JSON-LD)でこのように書く(フォーマットを定めた<a href="https://www.w3.org/TR/annotation-model/">Web Annotation Data Model</a>仕様の<a href="https://www.w3.org/TR/annotation-model/#complete-example">Complete Example</a>から抜粋)。</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Annotation"</span><span class="p">,</span><span class="w">
</span><span class="nl">"motivation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"commenting"</span><span class="p">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"created"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2015-10-13T13:00:00Z"</span><span class="p">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"generated"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2015-10-14T15:13:28Z"</span><span class="p">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"body"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TextualBody"</span><span class="p">,</span><span class="w">
</span><span class="nl">"role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tagging"</span><span class="p">,</span><span class="w">
</span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"love"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Choice"</span><span class="p">,</span><span class="w">
</span><span class="nl">"members"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TextualBody"</span><span class="p">,</span><span class="w">
</span><span class="nl">"role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"describing"</span><span class="p">,</span><span class="w">
</span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"I really love this particular bit of text in this XML. No really."</span><span class="p">,</span><span class="w">
</span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text/plain"</span><span class="p">,</span><span class="w">
</span><span class="nl">"language"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w">
</span><span class="nl">"creator"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://example.org/user1"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SpecificResource"</span><span class="p">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://example.com/document1"</span><span class="p">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"List"</span><span class="p">,</span><span class="w">
</span><span class="nl">"members"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FragmentSelector"</span><span class="p">,</span><span class="w">
</span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xpointer(/doc/body/section[2]/para[1])"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>何となく分かると思う。「http://example.com/document1のページの<code>/doc/body/section[2]/para[1]</code>というXPointerで表現されるパラグラフ(サンプルがHTMLだったらもっと都合がよかった……。その場合はCSSセレクターを使うことになるだろう)に対して、『I really love this particular bit of text in this XML. No really.』というコメントを付けている」ことになる。</p>
<p>さて、当然の悩みとして、「http://example.com/document1が更新されて、コメントの対象が無くなってしまったら、または内容が変わってしまったらどうなるのだろう」というのが生まれる(Kindleはどうなるんだっけ? 全部消える?)。</p>
<p>解決として、Robust Linksと同じ方法を取るのはどうか、と思い至った。</p>
<h2 id="robust-links">Robust Links</h2>
<p>ウェブページはどれも、内容が変わり得るし、無くなってしまうことだってある。こういう性質を持つ、もっと言うとより「強く」持つ物を、僕等はよく知っている。コードだ。日記でソースコードリーディングや実装解説をしていてGitHub上のソースコードの特定の行へのリンクを張る時、<code>master</code>ブランチのURLを使ったりはしない。「その時点でのコミット」を指す、ハッシュダイジェスト入りのURLを使う。</p>
<p>Robust Link(安定したリンク)はこのアイディアをどのページヘのリンクにも適用したようなものだ。普段使う<code><a></code>要素を強化して、リンク切れに強くする。そのアイディアはおおよそ次の通り。</p>
<ol>
<li>普通の方法で<code><a></code>要素を使ってリンクを作る(<code><a href="https://github.com/mementoweb/robustlinks">mementoweb/robustlinks</a></code>)</li>
<li>その時点でのスナップショットへのURLを、<code>data-versionurl</code>属性として付加する(<code>data-versionurl="https://github.com/mementoweb/robustlinks/commit/314640710584fcf91b0af64112714edd9ca4cb32"</code>)</li>
<li>リンクを作成した日を、<code>data-versiondate</code>属性として付加する(<code>data-versiondate="2016-05-11"</code>)</li>
</ol>
<p>結果こうなる:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"https://github.com/mementoweb/robustlinks"</span>
<span class="na">data-versionurl=</span><span class="s">"https://github.com/mementoweb/robustlinks/commit/314640710584fcf91b0af64112714edd9ca4cb32"</span>
<span class="na">data-versiondate=</span><span class="s">"2016-05-11"</span><span class="nt">></span>mementoweb/robustlinks<span class="nt"></a></span>
</code></pre></div></div>
<p>これによって「このリンクは2016年5月11日に作られたmemento/robustlinksへのリンクで、その時点でのこのページ(リポジトリー)は<code>https://github.com/mementoweb/robustlinks/commit/314640710584fcf91b0af64112714edd9ca4cb32</code>を見れば再現できる」と見做すのだ。(細かくは色々あるので、プロジェクトのサイトを参照されたい、特に「こんな面倒なマークアップをしないといけないなんて、正気か?」と感じた人:<a href="http://robustlinks.mementoweb.org/">Robust Links</a>)。実際これで問題はなくて、この例のようにGitHubであれば僕達には馴染み深いし、そうでなくても<a href="https://archive.org/index.php">Internet Archve</a>のようなアーカイブサイト(魚拓サイト?)をポイントしておけば、(理想的には)リンク時点の物を再現できる。記事内に作ったリンクがある時切れてしまっても、これによって記事執筆時点でのリンク先を見て、記事内容を理解することができるわけだ(そして、そういうリンクを追加するJavaScriptライブラリーをRobust Linksプロジェクトは提供している)。</p>
<h2 id="robust-annotation">Robust Annotation</h2>
<p>この方法、アノテーションにも応用できることは、ここまで読めば、すぐに分かるだろう。「アノテーションの対象を示す物」はリンクに他ならない。さっきのWeb Annotationのサンプルの<code>target</code>オブジェクトに注目し、例えば次のように<code>versionurl</code>プロパティを足してやればよい。</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"created"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2015-10-13T13:00:00Z"</span><span class="err">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"generated"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2015-10-14T15:13:28Z"</span><span class="err">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SpecificResource"</span><span class="p">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://example.com/document1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"versionurl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://web.archive.org/web/20151001135202/http://example.com/document1"</span><span class="p">,</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"List"</span><span class="p">,</span><span class="w">
</span><span class="nl">"members"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FragmentSelector"</span><span class="p">,</span><span class="w">
</span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xpointer(/doc/body/section[2]/para[1])"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w">
</span></code></pre></div></div>
<p>これで、「たとえ対象ページが大きく変更されていても、少なくとも2015年10月13日時点でのhttp://example.com/document1に対するコメントとしては意味を理解できる」ということになる。元々Web Annotation Data Modelで<code>created</code>や<code>generated</code>、サンプルにはないが<code>modified</code>が定義されているので、Robust Linksの<code>data-versiondate</code>に相当する物は追加しなくてもよい。</p>
<h2 id="section-1">アノテーションの更新</h2>
<p>なるほど「古い」アノテーションを、コンテクスト込みで理解できるようになった。だがこれが嬉しいのは、そのページのコンテンツに深い興味を持っている人と、そのページを取り巻く環境に興味を持っている人くらいなのではないかと思う。言い方を変えると、大多数のライトな読者は、そんな「ちょっとした違い」でしかないところまで探求しないだろうと思う。それより、最新のページを見ている時に、古いバージョンへのアノテーションも含めた色々なアノテーションが見られたほうが楽しくはないだろうか。それを実現するにはどうしたらいいだろうか。</p>
<p>まず、最新のページに、全てのアノテーションを表示すること。これは、当然ながら、記事の一部が削除されたり、追加されてアノテーションしていた部分の位置が変わった時に対応できない。</p>
<p>全てのアノテーションについて、それが指し示している「バージョン」を調べ、各バージョンのウェブページを集める。それぞれについて、最新のページとの差分を求める。その差分によって、アノテーションの対象が消えているか、場所が移動しているか、内容が変更されているかを判断できれば、いいはずだ。変更については意味の理解も必要だから簡単にはいかないが、削除と位置変更くらいなら簡単なはずだ(もちろん、前後の文脈を視野に入れたアノテーションであれば、離れたパラグラフの削除などのせいで意味を持たなくなることはあるが)。</p>
<p>例を出そう。こんなHTML断片があったとする。バージョン1と呼ぼう。</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p><span></span>こんにちは。<span class="nt"></span><span></span>お元気ですか。<span class="nt"></span><span></span>わたしは元気です。<span class="nt"></span></p></span>
</code></pre></div></div>
<p>これに対して、こんな三つのアノテーションがあったとする(Web Annotation Data Modelの記法ではなく、CSSセレクターと日本語のかぎかっこを使う)。</p>
<dl>
<dt>アノテーション1</dt>
<dd>バージョン1の<code>*:nth-child(n) > *:nth-child(1)</code>(<code><span>こんにちは。</span></code>)に対して「普通の挨拶」というコメント</dd>
<dt>アノテーション2</dt>
<dd>バージョン1の<code>*:nth-child(n) > *:nth-child(2)</code>(<code><span>お元気ですか。</span></code>)に対して「珍しい挨拶」というコメント</dd>
<dt>アノテーション3</dt>
<dd>バージョン1の<code>*:nth-child(n) > *:nth-child(3)</code>(<code><span>わたしは元気です。</span></code>)に対して「つまらない挨拶」というコメント</dd>
</dl>
<p>このページは何か理由があって編集され、こんなバージョン2になったとする。</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p><span></span>お元気ですか。<span class="nt"></span><span></span>わたしは元気です。<span class="nt"></span></p></span>
</code></pre></div></div>
<p>「こんにちは。」は平凡すぎて恥ずかしくなったのかも知れない。それはさておき、先の二つのアノテーションは <strong>バージョン1に対する物</strong> なので、最新であるバージョン2のどこに対してコメントしているかは自明ではない。もし何も処理をせず、そのままバージョン2に対する物として適用してしまうと、<code><span>お元気ですか。</span></code>に対して、「普通の挨拶」というコメントが表示されてしまう。「珍しい挨拶」としてコメントしたはずなのに。明らかに、アノテーション作成者の意図を捻じ曲げてしまっている。</p>
<p>これを是正するためには、何らかの処理を施して次のようになってほしい(名前にプライム記号「′」を付けた)。</p>
<dl>
<dt>アノテーション1′</dt>
<dd>(なし)</dd>
<dt>アノテーション2′</dt>
<dd>バージョン2の<code>*:nth-child(n) > *:nth-child(1)</code>に対して「珍しい挨拶」というコメント</dd>
<dt>アノテーション3′</dt>
<dd>バージョン2の<code>*:nth-child(n) > *:nth-child(2)</code>に対して「つまらない挨拶」というコメント</dd>
</dl>
<p><code>nth-child(2)</code>だった所が<code>nth-child(1)</code>に、<code>nth-child(3)</code>だった所が<code>nth-child(2)</code>に変わっている。</p>
<p>これを実現するためには何が分かるといいだろうか。「<code>*:nth-child(n)</code>(<code><p></code>要素)の<code>1</code>番目の子要素が無くなった」ということが分かればいいはずだ。すると、<code>1</code>番目より後だった要素の<code>nth-child()</code>の中を一つ減らせばいいということが分かる。単純な引き算だ。</p>
<p>ではそうしたHTMLの変更をどうやって知ればいいだろうか。二つあると思う。</p>
<p>一つは、HTMLを編集する時に、同時にこうした「どのような操作か」という情報も作り出すこと。テキストエディターのようにユーザーが直接(?)テキストを編集してしまうタイプのアプリケーションでは無理そうだが、ユーザーの操作と結果の生成の間にギャップが大きくて、何らかのフックを掛けやすそうなWYSIWYGエディターではそれが可能かも知れない。</p>
<p>もう一つは、二つのHTMLを比べて「差分」を抽出すること。テキストファイルに対する<code>diff</code>コマンドのような物だ。ここではHTMLなので、DOMツリーの差分ということになる。僕はこちらに期待している。</p>
<p>というわけで、「バージョン」間のDOMツリーの差分を計算することができれば、古めのアノテーションのうち、最新版でも有効なものを(ある程度)抽出して適用させることができる、つまり対象ページの履歴を見るだけで「アノテーションの更新」を実現できるのではないか、と思っているわけだ。</p>
<p>実はこの話は前にもしたことがあって、「<a href="../01/02.html">EPUB書籍に正誤表を反映する(Rubyスクリプトで)、またはEPUBのパッチプログラムの試み</a>」の「追記」に言及がある。また、こうした関心とは無関係に、フロントエンドエンジニアの<a href="https://twitter.com/kitak">@kitak</a>さんから「アプリケーション開発やデバッグの簡単のためにDOMツリーの差分を表示するツールが欲しい」という声を聞いたこともあって、今、DOMツリーの差分計算は、結構、ニーズがあるのではないかと思っている。</p>
<p>なお、補足だが、アノテーションを必ずCSSセレクターなどのDOM構造を前提とした方法で行わなければいけないわけではない。Web Annotation Data Modelでも、「先頭から何文字目か」でアノテーションを付ける方法も提供している。しかし、そうした「位置情報」を使っている時に、場所の移動を計算するのは、HTMLの場合はだいぶ大変だと思う。</p>
https://diary.kitaitimakoto.net/2016/05/11.html
Robust Annotation(安定したアノテーション)
2016-05-11T00:00:00Z
2016-05-11T00:00:00Z
<p><a href="https://sendagayarb.doorkeeper.jp/events/44607">Sendagaya.rb #148</a>@<a href="http://www.lanches.co.jp/">株式会社ランチェスター</a>に参加して来た。</p>
<p><a href="02.html">先週</a>同様、前半は<a href="http://railsguides.jp/security.html">Rails セキュリティガイド</a>の<a href="http://railsguides.jp/security.html#%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%81%A8%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB">4 リダイレクトとファイル</a>、後半は<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby</a>の「4.5.3 Methodオブジェクト」を読んだ。</p>
<p>「お酒は九時になってから」という謎ルールのもと、おもむろに持ち込んでいた(!)ビールを開け始めるメンバーに混じって(僕はお酒を飲めない)、pryで遊びながら<a href="http://docs.ruby-lang.org/ja/2.3.0/class/Method.html">Method</a>や<a href="http://docs.ruby-lang.org/ja/2.3.0/class/UnboundMethod.html">UnboundMethod</a>の理解を深めた。<code>include</code>によって生えたメソッドを無効化して、元の(より親側のクラスの)メソッドに戻す、というのの実装が激しかった。</p>
<p>次回は#149、ではなくて、特別編らしい:<a href="https://sendagayarb.doorkeeper.jp/events/44390">Sendagaya.rb #川村さんを囲む会</a></p>
<p>たぶんRubyとか関係ない。</p>
https://diary.kitaitimakoto.net/2016/05/09.html
Sendagaya.rb #148
2016-05-09T00:00:00Z
2016-05-09T00:00:00Z
<p><a href="https://sendagayarb.doorkeeper.jp/events/44004">Sendagaya.rb #147</a>@<a href="http://www.lanches.co.jp/">株式会社ランチェスター</a>に参加して来た。</p>
<p>前半、<a href="http://railsguides.jp/security.html">Rails セキュリティガイド</a>の<a href="http://railsguides.jp/security.html#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%AA-csrf">3 クロスサイトリクエストフォージェリ (CSRF)</a>の所を読んだ。話している中で出て来て知ったのけど「Railsゆとり」っていい言葉だな。</p>
<p>後半は<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby</a>のブロックの所を読んだ。Procとlambdaの違いなどをやった。</p>
<p>次回もこの二本立ての予定とのこと。参加申込はこちら:<a href="https://sendagayarb.doorkeeper.jp/events/44607">Sendagaya.rb #148</a></p>
https://diary.kitaitimakoto.net/2016/05/02.html
Sendagaya.rb #147
2016-05-02T00:00:00Z
2016-05-02T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/44055">Groonga新リリース自慢会 6.0.2</a>@<a href="https://crowdworks.co.jp/">クラウドワークス</a>に参加して来た。</p>
<p>Groongaは毎月29日に新バージョンをリリースしているので、今日は一足早いお披露目ということになる。主な変更点は次のページにまとまっている:</p>
<p><a href="http://groonga.org/ja/blog/2016/04/29/groonga-6.0.2.html">Groonga 6.0.2リリース</a></p>
<p>主に<a href="http://twitter.com/ktou">@ktou</a>さんが自慢していってくれたのだけど、追加や修正されたうち<a href="http://blog.createfield.com/">naoa_y</a>さんが実装した物も多かったようで、一緒に参加していたnaoa_yさんに自慢してもらった物も結構あった。今回の個人的な目玉はそのうちの多段ドリルダウンだった。自ら詳しい紹介を書いているのでそちらを参照されたい:<a href="http://blog.createfield.com/entry/2016/04/27/200305">Groonga 6.0.2から多段ドリルダウンが利用可能に</a>。naoa_yさんはユーザーにとってキャッチーな新機能を実装してくれることが多くありがたいことだ。</p>
<p>もう一つの個人的な目玉は、PGroongaの各種機能追加だ:<a href="https://pgroonga.github.io/ja/news/#version-1-0-7">1.0.7: 2016-04-24</a>。PGroongaは使っていないけど個人的にこの機能は欲しくて、GitHubでイシューを上げていたりする: <a href="https://github.com/groonga/groonga/issues/437">[RFC]API to retrieve query position in document #437</a>。早くGroonga(と言うかgroonga-httpd)にも入ってほしい。</p>
https://diary.kitaitimakoto.net/2016/04/27.html
Groonga新リリース自慢会 6.0.2
2016-04-27T00:00:00Z
2016-04-27T00:00:00Z
<p><img alt="Hypothes.isのバー" src="https://gyazo.com/c2c66762e491d0a02b466706f561d83d.png" style="float: right; margin-left: 1em; margin-bottom: 1em;" />
この日記は自分が読んでいて邪魔だなと感じる物はなるべく置かないようにしていて、ツイートとかシェアとかのボタンがないのはそのため。コメントについてはずっと迷っていたけど、まあ、置いてみようかということにした。静的サイトジェネレーターと組み合わせるコメントソリューションとしては<a href="https://gyazo.com/c2c66762e491d0a02b466706f561d83d">DISQUS</a>がデファクトスタンダード(ドファクトスタンダール?)だと思うけど、気になっていた<a href="https://hypothes.is/">Hypothes.is</a>を試すことにした。ページ右端に見えているエリアがそれだ。タイトルを隠してしまったりして「邪魔」なのは間違いないのだけど好奇心が勝ってしまった。タイトルの位置は後で調整する(かも)。</p>
<p><img alt="記事中の文章を選択するとアノテーションとハイライトのアイコンが現れる。" src="https://gyazo.com/a32998784be0efafe88123489493fe23.png" style="float: left; height: 4em; margin-right: 1em; margin-bottom: 1em;" />
記事の適当な所を選択するとAnnotateとHighlightのアイコンが出てきて、コメントを付けたりハイライトしたりできる。他人が付けたコメントについては、右端のバーを引っ張りだすことで見られる(コメントを残す時に、ここに現れてもよいかどうかを選べる)。</p>
<p>Hypothes.isはウェブページなどにアノテーションを付けられるようにしようというプロジェクトで、そのための埋め込みJavaScriptコードやChrome拡張なんかを作っている。アノテーションというのは、ページの全体や一部分をハイライトしたり、コメントを付けたりすること。はてなスターとかはてブみたいなイメージ。W3Cが<a href="https://www.w3.org/annotation/">Web Annotation</a>としてこうしたニーズのための仕様を策定中だったりする(Hypothes.isがWeb Annotationに従っているかは分からない。その前身のOpen Annotationに従ってはいたと思うので、プランとして追従するつもりはあるんだと思う)。アノテーションについては<a href="http://code.kzakza.com/">かざかざ</a>さんの素晴らしい記事を参照されたい。リンク先末尾の「関連エントリ」もぜひ辿ってほしい。</p>
<p><a href="http://code.kzakza.com/2013/08/open-annotation_data_model/">日本よっ!これがOpen Annotationだっ!!</a></p>
<p>ウェブページごと(URIごと)にフィードを吐いているので、このページに付けられたコメント一覧」みたいな物をページ末尾に付けたりすることはできるので、後でちょっとやってみようと思う(邪魔だったら公開しない)。</p>
<p>しかしまあ、Hypothes.isへの登録が必要なところとか、そもそもこの日記でコメントを残したいのかとか、使われるか不安はあるが、まあ、いいか。</p>
https://diary.kitaitimakoto.net/2016/04/10.html
日記のコメント用にHypothes.isを埋め込んでみた
2016-04-10T00:00:00Z
2016-04-10T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/41015">Groongaで学ぶ全文検索 2016-03-25</a>に行って来た。</p>
<p>今日は、仕事でPDFを全文検索できるようにしたいから話を聞きに来たという参加者がいたので、PDFを全文検索できるよう、Groongaのデータベースを作るまでをその場でやった。</p>
<p>まず、PDFを全文検索するために必要なことの概要を説明した。</p>
<h2 id="section">全文検索できるようにするまでの概要</h2>
<p>PDFを全文検索するには</p>
<ol>
<li>全文検索できるようにするための準備(データベースの構築)</li>
<li>データベースを使って全文検索をする</li>
</ol>
<p>という二段階が必要になる。</p>
<p>準備は、</p>
<ol>
<li>PDFからテキストを抜き出す</li>
<li>テキストをGroongaに突っ込む</li>
<li>Groongaが(勝手に)インデックスを作る</li>
</ol>
<p>という手順に分解できる。ここのところ僕が説明したのだけど、「テキストをGroongaに突っ込む」のところ、「どのような形で」というのが抜けていて、そこがぴんとこなかったようだ。あとで<a href="https://twitter.com/ktou">@ktou</a>さんとの質疑応答で「<strong>PDFのパスとかのメタデータと一緒に</strong>(JSONとかのフォーマットにして)突っ込む」と言ったところでぴんときたようだった。</p>
<p>手順に戻って、全文検索の実行のところ。</p>
<ol>
<li>検索語をGroongaに与える</li>
<li>Groongaがその語を含むPDFのパス(とか、ページ番号とかの付随的な物)を返す</li>
</ol>
<p>ということになる。</p>
<h2 id="pdf">PDFを全文検索できるようにする</h2>
<p>ここまで説明した所で、手元のPDFファイルを使って実際にデータベースの作成から検索してみるまでを@ktouさんがやって見せてくれた。時間も限られているので、「本文に関する検索をして、そのPDFのパスが得られるようにする」というのを目標にした。</p>
<h3 id="section-1">1. テーブルを作る</h3>
<p>コマンドラインでテーブルを作る(暗黙に、Groongaはテーブルの形でデータを表現しているということになる)。</p>
<pre><code>table_create pdfs \
TABLE_HASH_KEY \
ShortText
</code></pre>
<p><code>ShortText</code>のところは、ハッシュテーブルのキーの型を決めている。</p>
<p>こんなファイルを作って、<code>groonga</code>コマンドに入力すると、<code>pdfs</code>テーブルが出来る。</p>
<p>まずデータベース(ファイル)を作る。</p>
<pre><code>% groonga -n /tmp/grn
</code></pre>
<p>次にファイルに書いたコマンドを読み込ませる。</p>
<pre><code>% groonga /tmp/grn < /tmp/table-create.grn
</code></pre>
<p>これでテーブルが出来る。ただ、今はハッシュテーブルのキーのところにしかデータが保存できないので、PDF本文を保存する場所をこのテーブルのカラムとして作る。</p>
<pre><code>column_create pdfs body \
COLUMN_SCALAR \
Text
</code></pre>
<p><code>COLUMN_SCALAR</code>の所は<code>COLUMN_SCALAR</code>か<code>COLUMN_VECTOR</code>かが選べて、このカラムに入るのが分割できない単位なのか、配列のようにその単位を複合させた物なのかで選ぶ。今回は、一つのキー(PDFファイルのパス)に複数の本文が入ることはないので、スカラーにしている。</p>
<h3 id="section-2">データを入れる</h3>
<p>で、実際にデータを入れる。</p>
<p>その前に、PDFからテキストの形式にしないといけない。更に、Groongaは(コマンドでは)JSONフォーマットでデータを入力するので、データを加工しなければならない。</p>
<p>ここでは、<a href="https://poppler.freedesktop.org/">Poppler</a>付属の<code>pdftotext</code>コマンドで抽出したテキストをファイルに保存して、それを使ってRubyで、ファイルのパス情報と一緒にJSONにしていた。本題ではないので割愛。以下のようなファイルをRubyで作って、<code>groonga</code>コマンドに読み込ませる。</p>
<pre><code>load --table pdfs
[
{
"_key": "path/to/pdf"
"body": "でかい本文"
}
]
</code></pre>
<h3 id="section-3">インデックス無しでの検索</h3>
<p>Groongaはインデックスを作らないでも検索できる。</p>
<pre><code>select --table pdfs \
--query body:@API
</code></pre>
<p><code>@API</code>の所が、(その前にある<code>body</code>カラムに対して)<code>API</code>で検索する、という指定になっている。インデックスが無い時は、ただの線形検索になる。@ktouさんの手元のPDF、手元のGroongaで試したところ、この検索には60ミリ秒くらい掛かった。インデックスを作って使うとこれより速いはずなので後で試してみる。</p>
<h3 id="section-4">インデックスを作る</h3>
<p>RDBMSでは、インデックスを張るには単に張りたいカラムを指定すればよかったが、Groongaではより検索方法に合わせた柔軟なインデックスが作れるように、色々パラーターを指定しながら自分でインデックスを作る。そのインデックスは、上でやったようなテーブルとして作る。</p>
<p>が、初めての時などはおすすめの設定があるので取り敢えずテンプレとしてそれを使えばよい。</p>
<pre><code>table_create terms \
TABLE_PAT_KEY \
ShortText \
--default_tokenizer TokenBigram \
--normalzer NormalizarAuto
</code></pre>
<p>全文検索する時は、(<code>pdfs</code>テーブルでハッシュテーブルにしていたところに相当するところが)パトリシアトライというのがおすすめなので、<code>TABLE_PAT_KEY</code>と書く。これはこのまま使っておけばよい。「<code>terms</code>」というのがテーブル名なので、ここだけ気分によって変えればよい。</p>
<p>次はカラム。</p>
<pre><code>column_create terms body_index \
COLUMN_INDEX|WITH_POSITION \
pdfs \
body
</code></pre>
<p>これもテンプレで、「<code>body_index</code>」は変えてよい。名前から想像付くように、<code>pdfs</code>テーブルの<code>body</code>カラムのためのインデックスだからこの名前になっている。分かりやすいのがいいだろう。「<code>pdfs</code>」は、<code>pdfs</code>テーブルに対するインデックスということを意味し、「<code>body</code>」はその中の<code>body</code>カラムへのインデックスだということをGroongaに教えている。</p>
<p>これでさっきのように</p>
<pre><code>select --table pdfs \
--query body:@API
</code></pre>
<p>すると、今度は0.2ミリ秒程度で検索結果が返って来た。二桁の差が付いたことになる(PDF結構でかくて、1MiBくらいあった)。</p>
<p>あとは、アプリケーションの要件に合わせてウェブUIを付けたりすると出来上がりだ。</p>
<p>参考までに既にGroongaを使ったPDF検索アプリケーションとして<a href="http://honyomi.nagoya/ja/">honyomi</a>があって、検索結果からそのまま読み始めたりできるので便利。</p>
https://diary.kitaitimakoto.net/2016/03/25.html
取り敢えずPDFを全文検索するシステムのための最少ステップ
2016-03-25T00:00:00Z
2016-03-25T00:00:00Z
<p><a href="https://shibuyarb.doorkeeper.jp/events/40976">渋谷.rb[:20160316]</a>@<a href="https://pixta.co.jp/">ピクスタ株式会社</a>に行って来た。道に迷って三十分遅刻して、着いたらちょうど自己紹介が終わるところだった。ある意味ちょうどいい。</p>
<p>今日は特に誰かが発表することはなく、各々話をしたりもくもくしたりしていた。僕もRailsまだかなあとかGoとかフレームワークとかの話に混ぜてもらいつつ、<a href="https://github.com/rails/rails/tree/master/actioncable">Action Cable</a>を試すために<code>rails new</code>していた。</p>
<p>READMEに従ってコーディングしていたけどいつまで経ってもプロセスを起動する所に辿り着かなくておかしいなあと思って、「今(Rails 5 Beta 3)のAction Cableの入門に一番いいのって何なんですかね?」と聞いてみたら<a href="http://www.sitepoint.com/action-cable-and-websockets-an-in-depth-tutorial/">Action Cable and WebSockets: An in-Depth Tutorial</a>という記事を教えてもらった。あと実は、READMEの下の方にプロセスの起動について書いてあったということも教えてもらった。</p>
<p>最後に<a href="https://twitter.com/tyabe">@tyabe</a>さんが渋谷Ruby会議02を考えてます、って案内していた。ウェブアプリケーションフレームワークをテーマにする予定だとか。</p>
https://diary.kitaitimakoto.net/2016/03/16.html
渋谷.rb[:20160316]
2016-03-16T00:00:00Z
2016-03-16T00:00:00Z
<p><a href="https://sendagayarb.doorkeeper.jp/events/40813">Sendagaya.rb #141</a>で<a href="http://www.lanches.co.jp/">株式会社ランチェスター</a>に行って来た。今回は『<a href="https://www.oreilly.co.jp/books/9784873115894/">SQLアンチパターン</a>』に関連して相談したいことがあるという参加者がいたので、その話を前半(というか殆ど)やって、後半は『<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby</a>』を読んだ。</p>
<h2 id="sql">SQLアンチパターン</h2>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/SQL%E3%82%A2%E3%83%B3%E3%83%81%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-Bill-Karwin/dp/4873115892%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873115892" target="_blank"><img src="http://ecx.images-amazon.com/images/I/41qHKrFZi0L._SL160_.jpg" width="117" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/SQL%E3%82%A2%E3%83%B3%E3%83%81%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-Bill-Karwin/dp/4873115892%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873115892" target="_blank">SQLアンチパターン</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/Bill+Karwin" target="_blank">Bill Karwin</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">オライリージャパン</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2013-01-26</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4873115892" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>僕はこの本を持っていないから具体的なことはあまり分からないけれど、「ポリモーフィック関連はよくない」と言っている箇所があって、ちょうど自分で作っている物でもポリモーフィック関連を使っている所があって、それで相談に来たということらしかった。本の該当箇所を説明してもらったり、<a href="https://twitter.com/tkawa">@tkawa</a>さんが電子版を持っていたのでプロジェクターで映して見せてもらったりしながら話を聞いていると、基本的には外部キー制約が使えなくなることが問題のようで、これの解決方法として交差テーブル(だっけ?)という考え方と単一テーブル継承という考え方を紹介していた。</p>
<p>ポリモーフィック関連はよくない、しかしではどうしたらいいだろうか、この本で提示されている解決策もあまりいいようには思われない、ということで、その人の扱っている具体的なテーブル構造なんかを教えてもらいながらみんなでああだこうだと言っていた。一般的な正解がないタイプの問題だし、ウェブのサービスだと将来要件が変わることは容易に想像できる、それも今の段階ではどう変わるか想像できないタイプの変わり方をするものだから、正解を探すのはますます難しい。<a href="https://twitter.com/iR3">@iR3</a>さんが、長年の経験からためになるアドバイスをするなどしていた。<a href="http://fukajun.org/">fukajun</a>さんもいいこと言ったり、もっと言いたいことありそうだった(前にもテーブル設計の時に言いたいこと残してそうだったのを思い出すに、RDBMS得意そう)のだけど、残念ながら体調不良でidobataによるテキストチャットでの参加だったものだから、勿体無い感じなってしまった。勿体無い。</p>
<p>こういうのって、誰かに相談できる、話し相手がいることその物が価値だったりするものだなあという感覚を思い出した。</p>
<h2 id="ruby">メタプログラミングRuby</h2>
<p>最後三十分くらいで『メタプログラミングRuby 第2版』を読んだ。今回は主に<code>method_missing</code>の所だ。「<code>method_missing</code>を定義する時は一緒に<code>respond_to_missing?</code>も定義しましょう」というのがポイント。</p>
<p>あと、ついエイリアスメソッドチェインという言葉を使ってしまって「それ何?」って聞かれたのだけど、2016年なんだし、ほぼ説明を要するような言葉は封印していくべきだと感じた。</p>
<p>次回は<a href="http://rubyonrails.org/doctrine/">The Rails Doctrine</a>(多分、<a href="http://postd.cc/rails-doctrine/">日本語訳</a>のほう)を読みながらおしゃべりする予定:<br />
<a href="https://sendagayarb.doorkeeper.jp/events/41208">https://sendagayarb.doorkeeper.jp/events/41208</a></p>
https://diary.kitaitimakoto.net/2016/03/14.html
SQLアンチパターン@Sendagaya.rb #141
2016-03-14T00:00:00Z
2016-03-14T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/40088">Groongaで学ぶ全文検索 2016-03-11</a>に行って来た。</p>
<p>今日は、実際のユースケースに沿った話をしようということで、参加者のすごい具体的な業務の話をして、それにGroongaを入れる時にどういうふうにすればいいか、どこに注意すればいいかといった話をした。書けなさすぎるので、Mroongaを使ってウェブ日記に類似記事機能を入れるという喩え話にして書く。</p>
<p>この日記は、実際には静的HTMLをGitHub Pagesでホストしているが、仮に、MySQLに記事データを入れてそこからデータを引っ張って表示しているとしよう。その記事テーブルにはこんなカラムがあるだろう。</p>
<ul>
<li>URL(パス)</li>
<li>タイトル</li>
<li>タグ</li>
<li>本文</li>
</ul>
<p>各記事の末尾に、その記事に関連する記事一覧を出したいとする。</p>
<h2 id="section">関連記事の分類</h2>
<p>読者に読むべき記事を提示する際、状況を二つに分けられる。</p>
<ul>
<li>読者が、自分が読みたい物を自覚している(「Groongaの記事を読みたい」)</li>
<li>読者は自分が読みたいタイプの記事を自覚していない(「この人の文体は気持ちがいい。次に何を読もう」</li>
</ul>
<p>前者の読者に対しては、テキストフィールドを使ったいわゆる検索機能を提供すればよい。</p>
<p>後者の読者には、記事の提供側(個人日記なので、僕のことだ)が何らかの基準で読むといいだろう記事を提示する。提示のアプローチは更にこう分類できる。</p>
<ul>
<li>全ユーザーに共通の記事を提示する
<ul>
<li>僕がおすすめしたい記事一覧を出す</li>
<li>日記の全記事中、人気のある記事一覧を出す(例えばPVを使って)</li>
<li>今読んでいる記事に似た記事一覧を出す</li>
</ul>
</li>
<li>読者ごとにパーソナライズしたおすすめなどを提示する(例えばクッキーを使って。今日はこの話はしない)</li>
</ul>
<h2 id="section-1">類似文書検索を行う方法</h2>
<p>このうち「今読んでいる記事に似た記事一覧を出す」というのは類似文書検索と呼ばれる一般的な検索方法を使うと可能で、Groonga(Mroonga)にはその機能が備わっているので、ここではそれを使う時の方法と注意点を述べる。</p>
<p>まず、検索エンジンの用意だが、Mroongaを使えば簡単だ。MroongaはMySQLに組み込んで使えるストレージエンジンで、Groongaを使った検索機能などを提供する。Mroongaで日記テーブルを作って記事の表示や検索をさせることができるが、記事追加といったデータを扱うマスターデータベースはInnoDBにし、MySQLのレプリケーション機能を使って作るレプリカをMroongaにする、ということができる。するとInnoDBの堅牢さやトランザクション機能にあやかりながらGroongaの高速検索機能を使える。データはレプリケーション機能を使って勝手に入ってくるので、自分で同期の仕組みを作る必要もない。</p>
<p>こうしておくと、記事を表示する時にレプリカであるMroongaから類似文書検索機能を使って似た記事一覧を出すことができる。Mroongaで類似文書検索をするには、SQLで</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span> <span class="k">MATCH</span><span class="p">(</span><span class="err">本文</span><span class="p">)</span> <span class="n">AGAINST</span><span class="p">(</span><span class="err">今見ている記事の本文</span> <span class="k">IN</span> <span class="k">NATURAL</span> <span class="k">LANGUAGE</span> <span class="k">MODE</span><span class="p">)</span> <span class="p">...</span>
</code></pre></div></div>
<p>と書く。<code>AGAINST</code>に記事の本文を与えることと、モードを<code>NATURAL LANGUAGE MODE</code>にすることがポイントだ(ここで「<code>NATURAL LANGUAGE MODE</code>にすると類似文書検索になる」というのはMroongaがそうしてあるということで、MySQL一般のことではない)。</p>
<p>それから、トークナイザーに、MeCabを使うことも重要だ。N-gramだと精度の著しく低い結果になってしまう。どうしてか。それを説明するには類似文書検索の概要を説明する必要がある。(時間があったら書く -> なかった)</p>
<h2 id="mroonga">Mroongaでの類似文書検索の注意点</h2>
<p>今見ている記事の本文を使って類似文書検索をし、似ている記事の上位n件を表示すると、そこに今見ている記事その物が入ることが非常に多い(当たり前だ)。なので、条件句でそれを除くようにしておく。</p>
<p>「似ている」ということの精度を高めるために、本文以外の情報を使うとよい。例えば、タグが共通している記事<del>はスコアを上げる</del><ins>のみを提示する</ins>など(Groongaではできるのだが、Mroongaではスコアをどうこうというのはできないらしい)。</p>
<h2 id="section-2">その他の記事提示方法</h2>
<p>始めの分類の「読者が、自分が読みたい物を自覚している」という状況のため、検索エリアを設けたとする。検索語を入れている<strong>途中で</strong>、この日記の中で見つかりそうな単語を補完して提示すると、<strong>見栄えがいい</strong>し、検索しても無駄そうなキーワードも早めに分かってよい。</p>
<p>Mroongaの最新リリースで、これ(<del>サジェスト</del><ins>補完</ins>機能)を実現する機能が入った。ただ、MroongaはなるべくMySQLのユーザーが自然に使えることを重視している(上の<code>NATRUAL LANGUAGE MODE</code>など、既存のMySQLの物をうまくGroongaに当てはめて使っている)が、この機能はそうはいかないので、強引にGroongaのフル機能を使えるようにする。Groongaの機能を自由に使うには</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span> <span class="k">MATCH</span><span class="p">(...)</span> <span class="n">AGAINST</span><span class="p">(</span><span class="s1">'*SS title:@"..." && point >= 100'</span> <span class="k">IN</span> <span class="nb">BOOLEAN</span> <span class="k">MODE</span><span class="p">)</span> <span class="p">...</span>
</code></pre></div></div>
<p>と、<code>AGAINST</code>の中を<code>*SS</code>で始める。これを使ってGroongaのprefix_rk_search(prefix romaji kana search)関数を使うと、上の<del>サジェスト</del><ins>補完</ins>機能を実現できる。prefix_rk_searchはGroongaには以前のバージョンからあったので、「Mroongaの最新のリリースで<del>サジェスト</del><ins>補完</ins>機能を実現する機能が入った」のではなく、「Groongaのフル機能を使えるようにするプラグマ(<code>*SS</code>のこと)が入った」というのが正しい。</p>
<p>この<code>prefix_rk_search()</code>は、ローマ字やかなを使って、<strong>漢字が入っているカラムに対して</strong>前方一致検索ができる、という機能だ(だいぶ雑な説明。詳細はドキュメントを読まれたい:<a href="http://groonga.org/ja/docs/reference/operations/prefix_rk_search.html">http://groonga.org/ja/docs/reference/operations/prefix_rk_search.html</a>)。</p>
<p>ただ、これは通常の検索と違い、検索キーワード入力が終わる前に何度もリクエストを投げるのでそこには注意が必要だ。検索専用サーバーを立てるなどの対応が必要になるかも知れない。</p>
<p>ユーザーの入力を楽にするまた一つの方法として、やはり最新のリリースで、曖昧検索も入った。この日記では「Groonga」というキーワードをよく使っているので、検索すると何件か記事がヒットする。検索時に「Groonag」と綴り間違いをしてしまうかも知れない(僕はこの間違いをよくする。本当によくする)。それでも「Groonga」で検索した時の結果を出してしまう機能が曖昧検索だ。これの詳細は開発者のブログ記事を参照されたし:<a href="http://blog.createfield.com/entry/2016/02/28/014432">http://blog.createfield.com/entry/2016/02/28/014432</a></p>
https://diary.kitaitimakoto.net/2016/03/11.html
Groongaの類似文書検索
2016-03-11T00:00:00Z
2016-03-11T00:00:00Z
<p>今日も<a href="https://sendagayarb.doorkeeper.jp/events/39806">Sendagaya.rb #139</a>@<a href="http://www.lanches.co.jp/company">株式会社ランチェスター</a>に参加して来た。『<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby 第2版</a>』を少し読んだ後、参加者の持ち込んだ相談事の話をしたりした。</p>
<h2 id="ruby-methodmissing">メタプログラミングRuby method_missing</h2>
<p>今日は「3.3 method_missing」から。</p>
<p><a href="http://docs.ruby-lang.org/ja/2.3.0/method/Hash/i/default.html">Hash#default</a>なんてあったんだ、とか、引数なんて取れたんだ、と話していたが、本筋は問題なく読み進んでいた。</p>
<h2 id="section">相談事</h2>
<p>その後は参加者の一人が持ち込んだ相談について話した。ただ、業務と直結していてここに書いていいか分からないので割愛。面白い話だったし、実際やってみてどうなったか、後日ぜひ聞きたい。</p>
<h2 id="sendagayarb-kpt">Sendagaya.rb KPT</h2>
<p>次回は第140回というまことに切りのいい数値なので、<a href="http://fukajun.org/">fukajun</a>さんの発案で、KPTをやることになった。多分、飲みながら。</p>
https://diary.kitaitimakoto.net/2016/02/29.html
Sendagaya.rb #139
2016-02-29T00:00:00Z
2016-02-29T00:00:00Z
<p>この日記の検索機能(フッターの検索フォームからできる)では<a href="http://groonga.org/ja/docs/reference/executables/groonga-httpd.html">groonga-httpd</a>を使っている。日記本文は<a href="https://pages.github.com/">GitHub Pages</a>にホストしてもらっている。GitHub PagesはHTTPでもHTTPSでもどちらでもアクセスできて、ツイッターなどでURIを貼る時には僕はHTTPSの方を使っている。だがgroonga-httpdはこれまでHTTPSに対応していなかったので、<a href="https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest">XHR</a>で接続することができなかった。仕方なく前段に<a href="http://nginx.org/">Nginx</a>を立てて、そこでTLSの終端をしていた。</p>
<p>日課の<code>apt-get update && apt-get upgrade</code>をしていたらGroongaの各種パッケージが降って来たので更新内容を確認しに行った(<a href="http://groonga.org/ja/docs/news.html#release-6-0-0">6.0.0リリース - 2016-02-29</a>)。そこにTLSサポートのことが書かれていたので、早速この日記の検索サーバーでもアップデートして、TLSを有効にした(と書くと白々しいか、TLSサポートは僕が要望した物だった:<a href="https://osdn.jp/projects/groonga/lists/archive/dev/2016-February/003951.html">https://osdn.jp/projects/groonga/lists/archive/dev/2016-February/003951.html</a>)。</p>
<p>また、groonga-httpdは前々からHTTP/2が使えるようになっていたので、ついでにそちらも有効にした。</p>
<p>検索は単純にキーアップイベントを拾い、入力の一文字目からその都度groonga-httpdに検索リクエストを投げるようにしているので、HTTP/2でコネクションを張りっぱなしにして検索できるのは効果が大きいのではないかと思う(測ってない)。</p>
<p>ただ、検索用に日記のデータを入力するのは手元のスクリプトで<a href="https://github.com/ranguba/groonga-client">groonga-client</a> gemを使って実行していたのだが、このgemがHTTPS対応していないのでそこを対応しないといけない。それまでは、今日のこの日記以降の記事は検索対象にならない。HTTP接続用の別のポートを開けてもいいが、まあいいだろう。</p>
https://diary.kitaitimakoto.net/2016/02/27.html
日記の検索部分をHTTP/2対応した
2016-02-27T00:00:00Z
2016-02-27T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/39274">Groongaで学ぶ全文検索 2016-02-26</a>に行って来た。</p>
<p>今日のお題は「select」。<a href="http://groonga.org/ja/docs/reference/commands/select.html">select</a>というのは、Groongaの、全文検索エンジンとして最も重要な機能である検索を行うためのコマンド。</p>
<p>前回、実際のテーブルやデータを見ながら話せたのがよかったという声が多かったので、今回も具体的なGroongaの使い方を、というところからの選択。</p>
<p>まず僕が使っている以下の機能を簡単に紹介したあとで、それ以外のを<a href="https://twitter.com/ktou">@ktou</a>さんが紹介するという流れだった。</p>
<p>僕が使っている機能:</p>
<ul>
<li>全文検索</li>
<li>完全一致検索 … <a href="https://github.com/ranguba/epub-searcher">EPUB Searcher</a>の削除機能でレコードを特定する時に使っている</li>
<li>snippet_html出力 … この日記で、検索結果中の検索語をハイライトしている</li>
<li>グルーピング(ドリルダウン) … <a href="https://github.com/ranguba/epub-searcher">EPUB Searcher</a>で著者一覧ページで、著者の下に本一覧を表示するのに使っている</li>
<li>カラムの重み付け … この日記の検索で、本文よりタイトルとタグでのヒットを重視している。</li>
</ul>
<p>以下、@ktouさんが紹介してくれた機能:</p>
<h2 id="section">クエリー言語として解析</h2>
<p>例えばGoogleで「A B C」で検索すると「Aが含まれるかつBが含まれるかつCが含まれるページを探す」という意味になる。「A or B or C」と検索すると「Aが含まれるまたはBが含まれるまたはCが含まれるページを探す」という意味になる。ツイッターで「groonga -lang:ja」みたいな検索を僕はよくしていて、ここでの「lang:ja」(日本語のツイート)「-」(を除外する)というのもある<ins>(Groongaでもこの記法そのまま使えるとのこと。<code>lang:ja</code>が<code>lang</code>テーブルに対する<code>ja</code>での検索になり、「<code>-</code>」を付けると除外になる)</ins>。こうした、クエリーの中に検索語以外の命令を含められる時、この命令を表現する物がクエリー言語と呼ばれる。Groongaにもこのクエリー言語を理解する能力がある。</p>
<h2 id="section-1">集計機能</h2>
<p>グルーピングした際、グループそれぞれについて、何件のレコードがヒットしたかを集計することもできる。件数を数えるほか、あるカラムの値の合計、平均、最大値、最小値を求めることもできる。</p>
<p>忘れていたけど、この、件数を取得して表示するのはEPUB Searcherで使っていた。</p>
<h2 id="section-2">クエリー展開</h2>
<p>「焼き肉」で検索した時、勝手に「焼き肉 OR 焼肉 OR やきにく」というクエリーに変更して検索してくれる機能。「焼き肉と焼肉とやきにくを同一視する」ということ自体はユーザー(アプリケーション開発者)が予め登録しておく必要がある(どうやって?)。何を同一視したいかというのは、アプリケーションによって変わるからだ。</p>
<h2 id="section-3">重みの底上げ</h2>
<p>いい名前が付いていないと言っていた。</p>
<p>検索時にカラムごとに重みを設定して(タイトルは重くする、本文はそれほどでもない、など)最終的にレコードごとに総合スコアを出す。これが通常の重み付け。</p>
<p>その総合スコアの計算の<em>後に</em>、更に重みの数値を足すことができる。例えば、キャンペーンフラグカラムがONだと重み+10する、など。</p>
<p>通常の重み付けはカラムに対しての指定だが、この底上げはレコード一つ一つに対しての指定となる点が特徴となる。</p>
<p>使い方例も紹介されたが公開していいかどうか分からないとのことだったのでここでは他の例で説明する。Google検索では、過去に検索して閲覧したことがあるページが、次からの検索で上位になりやすいようになっている。これは、「閲覧した回数」といったカラムを持っておいて、総合スコアを計算し終わった後にそのカラムの値を追加して検索上位に持ってくる、というふうに、Groongaを使う場合は実装することができる。</p>
<h2 id="section-4">ソート</h2>
<p>ソートできる。</p>
<h2 id="section-5">途中から結果を出せる</h2>
<p>オフセットとかページネーションとかいうやつ。</p>
<h2 id="section-6">参照先でも検索できる</h2>
<p>SQLだとJOINするようなのがGroongaはJOINせずに検索できる。</p>
<p>記事テーブルとコメントテーブルがあって、記事に対してコメントが結び付いているとする。この時、SQLだと記事の主キーとコメントの主キーを<code>JOIN</code>で結び付ける。</p>
<ol>
<li>記事とコメントがくっついたテーブルを作る(と見做すことができる)</li>
<li>コメントテーブルの該当カラムに対して検索を実施し、</li>
<li>「くっついたテーブル」の記事テーブルの使いたいカラムを取得する</li>
</ol>
<p>となる。</p>
<p>Groongaは<del>コメントテーブルに記事への参照を</del><ins>記事テーブルにコメントへの参照を(発表していて逆だねって指摘してもらった)</ins>直接保持させることができる。だから、<code>JOIN</code>のような追加の命令無く</p>
<ol>
<li>コメントに対して検索し、</li>
<li>そのコメントが付いている記事を取得する</li>
</ol>
<p>ということができる。</p>
<p>記事に対して、後から関連商品みたいなテーブルを加えたくなることがあるかも知れない。この時、RDBMSであれば(正規化されていれば)単にそのためのテーブルを作成し、検索時に<code>JOIN</code>で結び付けるようにすることができる。記事テーブルには変更が必要ない。</p>
<p>Groongaの場合は記事テーブルに関連商品テーブルへの参照を持たせることになる(か逆方向の参照を持たせる。方向はアプリケーションによる)。既存の記事テーブルに対する変更が発生する。しかし、Groongaはカラム指向データベースなので、こうしたカラムの追加操作は低コストでできるので問題にならない(RDBMSで<code>ALTER TABLE</code>となると、件数によってはおおごとだ)。</p>
<p>「Solrはこうしたテーブル跨ぎの検索はうまくできないんでは?」と@ktouさんが言っていたので識者の指摘を待ちたい。</p>
https://diary.kitaitimakoto.net/2016/02/26.html
Groongaでできる検索方法あれこれ
2016-02-26T00:00:00Z
2016-02-26T00:00:00Z
<p><a href="https://tour.golang.org/">A Tour of Go</a>のエクササイズをやって、GitHubに上げてみた:<a href="https://github.com/KitaitiMakoto/a-tour-of-go-exercises">github.com/KitaitiMakoto/a-tour-of-go-exercises</a>。</p>
<p>ウェブクローラーの課題(<a href="https://github.com/KitaitiMakoto/a-tour-of-go-exercises/blob/master/exercise-web-crawler.go">exercise-web-crawler.go</a>)が難しくて、まずgoroutineを使って非同期にクロールさせるのに手こずった。一つのHTTPリクエストに一つのgoroutineを割り当てた時、終了の待ち合わせはどうするのがいいんだろう。僕は、一々チャンネルを閉じるようにした。閉じるのの待ち合わせは</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="k">range</span> <span class="n">ch</span> <span class="p">{}</span>
</code></pre></div></div>
<p>とやった。この他に、<code>go</code>呼び出しの辺りにラベルを付けて、groutineから戻ったところで<code>break</code>するというやり方もあるようだ。エクササイズは本当は教師がいて答え合わせしてもらえるととてもいいのだけど、残念ながらいないので、誰か、「こうしたらもっといいよ」というの教えてください。まあ、色んなソースを読むというのが、よいのだろうとは思うが。</p>
<p>次に、「一度フェッチしたURIは二度フェッチしないようにする」というのも課題の一部で、ヒントに「その管理にマップを使うのはいいけど、マップは単独では並行処理に関して安全ではない」とあって、この排他制御にもちょっと困った。大きなロックを獲得して、その中でフェッチすると、並行処理させている意味が無くなっちゃう。でも、どのタイミングでロックを取ればいいのか、何のロックを取ればいいのか、というのに迷った。「一つのURIにつき一つのミューテックスを作る」ということを最初考えたのだけど、そもそもあるURIに対応するミューテックスが存在するかの確認処理と、その後ミューテックスを作るまでの間に他のgoroutineが同じ物を触る可能性があるわけで、うまくいかない。結局、単純にマップその物をロックするようにした。そうすると、<code>defer</code>を使わない実装になってしまったのだけど、もっといいやり方がないものだろうか。</p>
https://diary.kitaitimakoto.net/2016/02/21.html
A Tour of Goのエクササイズをやってみた
2016-02-21T00:00:00Z
2016-02-21T00:00:00Z
<p><a href="https://sendagayarb.doorkeeper.jp/events/39087">Sendagaya.rb #137</a>に参加して来た。『メタプログラミングRuby 第2版』と、Active Record enumsの話をして来た。</p>
<h2 id="ruby-3-">メタプログラミングRuby 3章 メソッド</h2>
<p>今回も<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby</a>を読んだ。「3章 メソッド」から(こういう時、章にもリンクを貼りたいものだ)。</p>
<p>同じようなメソッドの定義を繰り返すのではなく、動的に定義することで、重複した記述を減らす方法が紹介される。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Computer</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">computer_id</span><span class="p">,</span> <span class="n">data_source</span><span class="p">)</span>
<span class="vi">@id</span> <span class="o">=</span> <span class="n">computer_id</span>
<span class="vi">@data_source</span> <span class="o">=</span> <span class="n">data_source</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">define_component</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="n">define_method</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span> <span class="k">do</span>
<span class="n">info</span> <span class="o">=</span> <span class="vi">@data_source</span><span class="p">.</span><span class="nf">send</span> <span class="s2">"get_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">_info"</span><span class="p">,</span> <span class="vi">@id</span>
<span class="n">price</span> <span class="o">=</span> <span class="vi">@data_source</span><span class="p">.</span><span class="nf">send</span> <span class="s2">"get_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">_price"</span><span class="p">,</span> <span class="vi">@id</span>
<span class="n">result</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="nb">name</span><span class="p">.</span><span class="nf">capitalize</span><span class="si">}</span><span class="s2">: </span><span class="si">#{</span><span class="n">info</span><span class="si">}</span><span class="s2"> ($</span><span class="si">#{</span><span class="n">price</span><span class="si">}</span><span class="s2">)"</span>
<span class="k">return</span> <span class="s2">"* </span><span class="si">#{</span><span class="n">result</span><span class="si">}</span><span class="s2">"</span> <span class="k">if</span> <span class="n">price</span> <span class="o">>=</span> <span class="mi">100</span>
<span class="n">result</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">define_component</span> <span class="ss">:mouse</span>
<span class="n">define_component</span> <span class="ss">:cpu</span>
<span class="n">define_component</span> <span class="ss">:keyboard</span>
<span class="k">end</span>
</code></pre></div></div>
<p>これで</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">mouse</span>
<span class="n">info</span> <span class="o">=</span> <span class="vi">@data_source</span><span class="p">.</span><span class="nf">get_mouse_info</span><span class="p">(</span><span class="vi">@id</span><span class="p">)</span>
<span class="n">price</span> <span class="o">=</span> <span class="vi">@data_source</span><span class="p">.</span><span class="nf">get_mouse_price</span><span class="p">(</span><span class="vi">@id</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="s2">"Mouse: </span><span class="si">#{</span><span class="n">info</span><span class="si">}</span><span class="s2"> ($</span><span class="si">#{</span><span class="n">price</span><span class="si">}</span><span class="s2">)"</span>
<span class="k">return</span> <span class="s2">"* </span><span class="si">#{</span><span class="n">result</span><span class="si">}</span><span class="s2">"</span> <span class="k">if</span> <span class="n">price</span> <span class="o">>=</span> <span class="mi">100</span>
<span class="n">result</span>
<span class="k">end</span>
</code></pre></div></div>
<p>みたいなメソッドを幾つも書く作業から開放される。例によってふんふんなるほどと読んでいたが、例によって甘かった。十五分読んだ後にみんなで話している時に、このコードの「危なさ」を指摘する声が上がった。</p>
<p>「<code>initialize</code>で<code>data_source</code>を引数に受け取っているが、別々のインスタンス初期化時に別々の<code>data_source</code>を受け取り得るから、クラス全体が和集合のような不要なメソッドも持った物になる」とのことだった。僕にはぴんとこなかった。その後、コードを使って説明してくれた。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">methods</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:moge</span><span class="p">,</span> <span class="ss">:hoge</span><span class="p">]</span>
<span class="n">methods2</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:moge</span><span class="p">,</span> <span class="ss">:hoge</span><span class="p">,</span> <span class="ss">:age</span><span class="p">]</span>
<span class="k">class</span> <span class="nc">Computer</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">methods</span><span class="p">)</span>
<span class="nb">methods</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="nb">method</span><span class="o">|</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">define_kick</span> <span class="nb">method</span>
<span class="nb">puts</span> <span class="nb">method</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">define_kick</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="n">define_method</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">_kick"</span><span class="p">)</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'name'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">c1</span> <span class="o">=</span> <span class="no">Computer</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">methods</span><span class="p">)</span> <span class="c1"># => "moge"、"hoge"を出力</span>
<span class="nb">p</span> <span class="n">c1</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">instance_methods</span><span class="p">(</span><span class="kp">false</span><span class="p">)</span> <span class="c1"># => [:moge_kick, :hoge_kick]</span>
<span class="n">c2</span> <span class="o">=</span> <span class="no">Computer</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">methods2</span><span class="p">)</span> <span class="c1"># => "moge"、"hoge"、"age"を出力</span>
<span class="nb">p</span> <span class="n">c2</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">instance_methods</span><span class="p">(</span><span class="kp">false</span><span class="p">)</span> <span class="c1"># => [:moge_kick, :hoge_kick, :age_kick]</span>
<span class="nb">p</span> <span class="n">c1</span><span class="p">.</span><span class="nf">class</span> <span class="o">==</span> <span class="n">c2</span><span class="p">.</span><span class="nf">class</span> <span class="c1"># => true</span>
<span class="nb">p</span> <span class="n">c1</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">instance_methods</span><span class="p">(</span><span class="kp">false</span><span class="p">)</span> <span class="c1"># => [:moge_kick, :hoge_kick, :age_kick]</span>
</code></pre></div></div>
<p>(少しコメントを足し、改変した)</p>
<p>最後の行、<code>c1</code>でも期待しないメソッド<code>#age_kick</code>が使えることを示している(その前の行からも明らかだが)。こういう危なさがあることに、僕は気が付かなかった。勿論、これが「危ない」かどうかは作るアプリケーション次第だ。別にこれで構わないということも理論上ある。それは何かと話していて、「サンプル」かなとなった。</p>
<h2 id="activerecord-enumsdb">ActiveRecord enumsのDB内での値を文字列にする</h2>
<p>その後、今日は何を話そうかと話していると、Rails 4.1から入った<a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#active-record-enums">ActiveRecord enums</a>の機能についての相談が上がった。</p>
<p>この機能はRDBMSなんかでも実装されているenum(列挙型)の機能をRailsのレイヤーで実装した物で、ユーザー(Railsを使うプログラマー)はモデルオブジェクトが提供する、人間が読みやすいインターフェイスだけ扱っていればいいようになる。バックエンドではActiveRecordが数値に変換してDBに格納し、ユーザーとの間を取り持ってくれる。</p>
<p>今回の相談は、この機能ではDBに格納する値も文字列にすることができるが、それは普通ではないのだろうか? という物。</p>
<p>始め、「何となく気持ち悪いですね」くらいしか言うことができなかった。一応、データサイズが増えるというのもあるが、このご時世、そこは気にするポイントではないだろうとのこと。しばらく話しながら思い付いたのは、文字列にするとインデックスを張ることになるだろうから、インサート時にインデックスを更新するコストが掛かって(遅くなって)しまうということだった。これはそれなりに妥当だと受け取られて、「じゃあ、やっぱり(ActiveRecordの)enumのバックエンドは数値にしておくのが普通なんですね」ということになった。</p>
<p>ちなみに相談者が文字列を使いたい一番の背景は、Railsは分からないがSQLなら分かるという人のいる場(に、今いるらしい)では、値が数値で返って来ると人間の方で対応する文字列を参照しないといけないので、嫌だ、ということだった。これは勿論妥当だと思うので、この人のケースでは、文字列を使うのは正解だと思う(が、<a href="https://github.com/sferik/rails_admin">RailsAdmin</a>がエラーになるという問題があるらしかった)。それは置いておいても割と一般的に文字列を使いたいこだわりがありそうだったが、そこは深く話さなかった。今思うともったいなかったかも知れない。</p>
<p>「そう言えば、Railsを使うとマスターテーブルを作ることをしなくなるな」という話もした。『<a href="https://www.oreilly.co.jp/books/9784873114163/">エンタープライズRails</a>』には「アプリケーションのフレームワークや言語よりも、データベースのデータのほうが長く残るから、データだけで完結できるようにしておくべきだ」と書いてあったように思うのだけど、時代も違うし、エンタープライズだとウェブのコンシューマー向けサービスとは領域も違うということだろうか。</p>
https://diary.kitaitimakoto.net/2016/02/15.html
Sendagaya.rb #137
2016-02-15T00:00:00Z
2016-02-15T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/38529">Groongaで学ぶ全文検索 2016-02-12</a>に行って来た。今日は、参加者が勉強にと国立国会図書館の書誌情報データを取って来てGroongaに入れてみている、だが今のテーブル設計がいいのか分からないということだったので、それをまず説明してもらって、<a href="https://twitter.com/ktou">@ktou</a>さんが各テーブル作成時のオプションなどを説明してくれた。</p>
<p>インデックスを作る前と後で検索結果を比較していて、インデックス作ってそっちで検索すると20倍速くなったということで、インデックスの有用性が証明されてた。</p>
<p>そんなわけで個別に色々説明してくれたのだが、まとまっているわけではないので、幾つか取り上げることにする。</p>
<h2 id="withposition">WITH_POSITIONの活躍</h2>
<p>今回はインデックステーブルを作る時に、タイトルに対応するインデックスカラムを作る時に<code>COLUMN_INDEX|WITH_POSITION</code>と、<code>WITH_POSITION</code>が指定されていた。これがうまく活きる状況の話があった。</p>
<p>『一、二年生の基礎英作文』というタイトルの本があった。これに対して「一年生」で検索した時に、この本はヒットするだろうか。但し、実際のトークナイザーは<code>TokenBigram</code>が選ばれていたが、仮に<code>TokenMecab</code>にしたとする。</p>
<p>タイトルは「一」「、」「二」「年生」「の」……とMeCabはトークナイズした。検索クエリーは「一」「年生」になる。とすると、このタイトルはクエリーのトークン「一」にも「年生」にもヒットするので、ヒットしそうだ。しかししない。なぜならGroongaはこのタイトルカラムではキーワードの位置情報も持っていて、「一」と「年生」がこの順番で隣り合っていないことを知っているからだ。同様の理由で、仮に『二年生の基礎英作文(一)』みたいなタイトルの本があったとしてもヒットしない。このように、「キーワードが文書中でどの位置にあるか」という情報も持たせるオプションが<code>WITH_POSITION</code>。なので逆に、これを指定していなければ上のクエリーでこの文書はヒットするだろう。</p>
<p>MeCabみたいな形態素解析をせずbi-gramで<code>WITH_POSITION</code>なし、みたいなカラムを作ると、色々な物がヒットし過ぎて検索には使い物にならないカラムになったりもする。</p>
<h2 id="bi-gram">bi-gramでトークナイズしている時は、一文字では検索できないのか?</h2>
<p>bi-gramでトークナイズしている時は、インデックスのキーには二文字の文字列が入っている(文末など場合によっては一文字もある)。そういう時に、検索クエリーが一文字の時には、何もヒットしなくなるのでは? という疑問が出た。</p>
<p>答えはヒットする。今回の実例では、インデックスキーをパトリシリアトライで作っていた(=ハッシュにしていなかった)から。パトリシアトライなら前方一致検索ができるので、一文字のクエリーでも探すことができる。なぜパトリシアトライなら前方一致検索ができるのか? 忘れた。もうパトリシアトライの構造を忘れたので後で見ておく……。</p>
<p>尚、MySQL 5.7から入ったInnoDBの全文検索機能では、bi-gramでトークナイズする設定にすると、実際に一文字での検索ではヒットしなくなるらしい。</p>
<h2 id="section">テーブルのキーに選ぶべき物</h2>
<p>今回は、国会図書館から提供されているTSVのうち、URL、タイトル、著者、出版社をGroongaに入れていた。テーブルとしてはハッシュテーブルを選び、キーにURIを入れていた。これは正しい設計だろうか?</p>
<p>具体的なアプリケーションの要件によって、考慮するべきことがある。</p>
<ul>
<li>ハッシュテーブルでいいだろうか?</li>
<li>いいとして、キーはURLでいいだろうか?</li>
</ul>
<p>ハッシュテーブルのいい所は、キーが存在していて、そのキーを使ってレコードを一意に特定できる所。逆に言うと特定する必要がない、つまり後からレコードを探して更新や削除をすることが無いのであれば、ハッシュテーブルを使う必要はない。追加しか無いのなら、配列を使ってもいい。</p>
<p>ハッシュテーブルを選んだとして、そのキーカラムのサイズは(現在のGroongaでは)4KiBになっている。普通それならURLを入れても大丈夫だろうと思うが、溢れることがあると@ktouさんは主張していた(何か嫌な思い出がありそうだった)。ので、今回の場合だと、選択したカラムにはなかったが、ISBNを使うのがよさそうとのこと。また、そういう一意かつ短めの物が選べない時は、一意な物のハッシュ値を計算してキーとして入れるといいとのこと。</p>
https://diary.kitaitimakoto.net/2016/02/12.html
実例を元にした全文検索エンジン(Groonga)のテーブル設計
2016-02-12T00:00:00Z
2016-02-12T00:00:00Z
<p>この日記に検索機能を付けてフッターに置いた。軽快にインクリメンタルサーチができていて、なかなかいい。</p>
<h2 id="groonga">Groongaを使った全文検索</h2>
<p>この際に、二つやったことのない作業があって、その一つは検索エンジンを使った検索機能の作成。正直、静的サイトの検索なんて<a href="https://developers.google.com/custom-search/">Google Custom Search</a>や<a href="https://swiftype.com/">Swiftype</a>を使えばいいと思うが、自分で遊ぶための場所を持つというのも日記を移行した目的の一つだったから、自分でやってみた。</p>
<p><a href="https://middlemanapp.com/jp/">Middleman</a>でサイトをビルドした後、<a href="http://docs.ruby-lang.org/ja/2.3.0/library/rake.html">Rake</a>タスクでビルド済みディレクトリーから日記本文を抽出して、<a href="http://groonga.org/ja/">Groonga</a>に投げ込んでインデックスを作っている。始め、Middlemanのビルド時のidentical、updated、created、removedという状態変化の情報を利用して、必要な分だけGroonga上のインデックスを更新しようかと思っていた(そのための調査結果はQiitaの<a href="http://qiita.com/KitaitiMakoto/items/ca3792f75270b533d37c">Middlemanで「変更なし」「作成」「削除」「変更」の状態を取る</a>という記事に書いた)。でも、実用上それで問題ないのだが、一応「ビルド環境が変わったらそういった状態変化の情報は変わる、ポータブルではない」ということに配慮して、毎回全日記の分をGroongaに投げるようにしている。今のところ、パフォーマンスが問題になったりということは全然ない。</p>
<p>そのように毎回全部作り直すことにしたので、ローカルでインデックス構築済み<a href="http://groonga.org/ja/docs/reference/executables/groonga-httpd.html">groonga-httpd</a>の<a href="https://www.docker.com/">Docker</a>イメージを作って、それをデプロイするようにしようかとも思った。実際イメージを作るまではやっていた。が、せっかくGroongaがなるべくOSの機能を引き出すように作られているのに、仮想環境で動かすのはもったいないと思って「普通」にインストールして<a href="http://www.freedesktop.org/wiki/Software/systemd/">systemd</a>で起動して使っている(<a href="https://www.linode.com/">Linode</a>を使っているから、まあ、仮想環境ではあるが)。</p>
<p>何故か<code>snippet_html()</code>関数がうまく動いていないので、別途調査が必要だ。</p>
<p>そう言えば、記事を削除した時の対応もRakeタスクにない。消すことがあったら考えよう。記事データをGroongaを投げる時に削除できるよう自動化するのでなく、PostgreSQLの<code>VACUUM</code>みたいに、ユーザー側で明示的に実行するのでいいと思う。</p>
<h2 id="polymer">Polymerコンポーネントの作成</h2>
<p>もう一つは、検索エリアを作るのに、<a href="https://www.polymer-project.org/1.0/">Polymer</a>を使って検索用のコンポーネントを作った(HTMLソースを見れば<code><blog-search></code>というタグが見付かるはずだ)こと。</p>
<p>作り始め、全く何も表示できず、Polymerに慣れてもいないので自分が何か間違っているかと色々試しすごい時間を費したが、内部で使っているコンポーネントを読み込む<code><link></code>要素の<code>import</code>属性を<code>ipmort</code>と書き間違えていただけだった。分かった時には脱力した。</p>
<p>ユーザー入力(<code><paper-input></code>)、Groongaとの通信(<code><iron-ajax></code>)、その二つの繋ぎ(<code><blog-search></code>)という構成で作ったが、<code><blog-search></code>は昔ながらのjQueryを使った素朴な同期処理のようになってしまった。Polymerにはデータバインディングの仕組みがあるのにもったいない。もう少しすっきりするよう書き換えたい。</p>
<p>しかし、他のコンポーネント指向のライブラリーもそうだと思うが、閉じたスコープのことだけを考えてHTML、CSS、JavaScriptを書けばよいというのは大分ストレスフリーだ。</p>
<hr />
<p>前からずっと、「あの記事はどこだっけ」と思った時に<code>git grep</code>してURIを調べてからページを表示していたので、それが無くなって自分が嬉しい機能だ。</p>
<h2 id="section">追記</h2>
<p>と思いきや、このサイトは外にリンクを置く時はHTTPSで置いているのだけどgroonga-httpdのdebパッケージにはTLSモジュールが組み込まれていなかった。取り敢えずNginxを前に立てたけど、groonga-httpdのウェブサーバー機能もNginxなので、こちらのビルド時にTLSモジュールを組み込むのが正しいと思う。後でメーリングリストに送って入れてもらうか自分でビルドするかしよう。</p>
https://diary.kitaitimakoto.net/2016/02/07.html
日記に検索機能をつけた
2016-02-07T00:00:00Z
2016-02-07T00:00:00Z
<p><a href="https://sendagayarb.doorkeeper.jp/events/38208">Sendagaya.rb #135</a>に行って来た。今日も前半は『<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby 第2版</a>』を読んで、後半はRails 5のチェンジログから<a href="https://github.com/rails/rails/blob/v5.0.0.beta1/activerecord/CHANGELOG.md">Active Record 5.0.0.beta1のチェンジログ</a>を眺めていた。</p>
<h2 id="ruby-refinements">メタプログラミングRuby Refinements</h2>
<p>今日は「2.4.2 メソッドの実行」から「2.4.3 Refinements」まで。</p>
<p>メソッドの実行では他の言語から来ると<code>private</code>とかに戸惑うよねとか、<code>protected</code>って何に使うんだろうねという話をした。</p>
<p>お次はいよいよ<a href="http://magazine.rubyist.net/?0041-200Special-refinement">Refinements</a>。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MyClass</span>
<span class="k">def</span> <span class="nf">my_method</span>
<span class="s2">"original my_method"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">anothor_method</span>
<span class="n">my_method</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">module</span> <span class="nn">MyClassRefinements</span>
<span class="n">refine</span> <span class="no">MyClass</span> <span class="k">do</span>
<span class="k">def</span> <span class="nf">my_method</span>
<span class="s2">"refined my_method"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">using</span> <span class="no">MyClassRefinements</span>
<span class="no">MyClass</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">my_method</span> <span class="c1"># => "refined my_method"</span>
<span class="no">MyClass</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">another_method</span> <span class="c1"># => "original my_method"</span>
</code></pre></div></div>
<p>この最後の行は驚かないだろうか。僕は驚いた。</p>
<p>この挙動を本では</p>
<blockquote>
<p>Refinementsが有効になっているコードは、(略)インクルードやプリペンドしたモジュールのコードよりも優先される。</p>
</blockquote>
<p>と説明している。しかしこれだけで、メソッド探索の順番を覚えられるだろうか。<a href="https://twitter.com/tkawa">@tkawa</a>さんの説明が素晴らしかった。</p>
<ol>
<li>クラスやインクルードやプリペンドを考慮するより先に、まずRefinementsを探す</li>
<li>次に通常のメソッド探索手順に従って、プリペンドされた物、インクルードした物、特異クラス、クラス……とメソッドを探す</li>
</ol>
<p>ということだ。上の例で言うと、まず<code>my_methodを呼ぶ場合</code></p>
<ol>
<li>Refinementsを探す</li>
<li><code>MyClassRefinements</code>が見付かる</li>
<li><code>my_method</code>が定義されている</li>
<li><code>MyClassRefinements#my_method</code>を呼ぶ</li>
</ol>
<p>で、結果は<code>"refined my_method"</code>になる。一方<code>another_method</code>は</p>
<ol>
<li>Refinementsを探す</li>
<li><code>MyClassRefinements</code>が見付かる</li>
<li><code>another_method</code>は定義されていない</li>
<li>Refinementsの探索は終わり、今後二度と探索されない</li>
<li>プリペンドされたモジュールやインクルードされたモジュールを探すが、ない</li>
<li>クラスを見る</li>
<li><code>MyClass</code>である</li>
<li><code>another_method</code>が定義されている</li>
<li><code>another_method</code>を実行する</li>
<li><code>my_method</code>が呼ばれている</li>
<li>Refinementsの探索は終わっているので、クラスの<code>my_method</code>が見付かる</li>
<li><code>MyClass#my_method</code>を実行する</li>
</ol>
<p>ということで、結果が<code>"original my_method"</code>になる、というわけだ。</p>
<p>子の説明を聞いて僕は「あ。」と声が漏れるくらい腹に落ちた。</p>
<h2 id="active-record-500beta1">Active Record 5.0.0.beta1チェンジログ</h2>
<p>その後もちょいちょいRefinementsと遊んでからは<a href="https://github.com/rails/rails/blob/v5.0.0.beta1/activerecord/CHANGELOG.md">Active Record 5.0.0.beta1のチェンジログ</a>をざっと流しながら気になった所で止めて、あーだこーだ言っていた。結構RDBMSの個別機能に対応していたり、細かなユースケースを拾ったりしていて、「Active Recordは基本は既に成熟しているんだろうなあ」という感想を持った。</p>
<p>次回は『メタプログラミングRuby』読むほか、fukajunさんがElectronについての発表をしたいということなのでそれは聞けるはずだ。あとはその場で決まるんだろう。その場で決まるので、聞きたいことを持って行けば、聞けると思う。</p>
<p>次回分もすぐさまイベントが作られて、申し込める。<br />
<a href="https://sendagayarb.doorkeeper.jp/events/38655">https://sendagayarb.doorkeeper.jp/events/38655</a></p>
https://diary.kitaitimakoto.net/2016/02/02.html
Sendagaya.rb #135
2016-02-02T00:00:00Z
2016-02-02T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/37647">Groongaで学ぶ全文検索 2016-01-29</a>に行って来た。今日は濃かった。その分面白かった。</p>
<p>まずノーマライザーの話をして、その後形態素解析の話をした。</p>
<h2 id="section">ノーマライザー</h2>
<p>ノーマライザーはノーマライズする物。ノーマライズは、半角「カレー」と全角「カレー」、ひらがな「りんご」とかたかな「リンゴ」など、厳密には違う物を同じ物に寄せるという処理。どういう基準でノーマライズするかというのは色々あって、その色々ごとにノーマライザーの種類がある。</p>
<p>インデックスを作る時、インデックスのキーにノーマライズした物を選んでおくと、例えば「りんご」で検索した時に、「りんご」を含む文書と「リンゴ」を含む文書の両方を返すことができる。これがノーマライザーを使う理由。</p>
<p>ただ、この状況下で「リンゴ」で検索すると、インデックスのキーワードには「りんご」しかないので、何もヒットしない(「リンゴ」というキーワードはインデックスを作る時にノーマライズされて「りんご」になっているから、入っていない)。だから、検索時にクエリーの方もノーマライズする。この時にはインデックス作成時と同じノーマライザーを使わないと、キーワードの集合が変わるので、いけない。</p>
<p>(面白い質疑応答二つあった、後で書く。かも。)</p>
<h2 id="section-1">形態素解析</h2>
<p>形態素解析は、文などを形態素に分ける処理。形態素が何かは各自調べられたい。迂闊なことを言えない。日本語の文を構成する、意味的に分割できる一番小さな単位、とかそんな感じ。全文検索では、インデックスのキーワードをどのように選ぶかというやり方の一つとして、形態素解析器を使う(他にN-gramがあるのは以前書いた通り:<a href="../../2015/11/20.html">日本語文書の全文検索</a>)。</p>
<p>例えばクエリーの中の「りんご」と「リンゴ」を同一視するには、文字単位で見ていって、かたかなを見付ける度に全部ひらがなにしていけばいい。こういうノーマライズは文字単位で処理すればできる。が、「PC」で検索した時に「パソコン」を含む文書を返す、二つを同一視するにはこの方法では不可能。「パソコン」を見つけたら「PC」にする必要がある。この時に</p>
<ul>
<li>「パソコン」が一つの単位になるように、クエリーを分割する方法(形態素に分割する方法)、「パソ」みたいに「パソコン」より細かく分割しないし、「パソコンを」みたいに他の語とくっついた形で大きな粒度で分割しない方法。</li>
<li>「パソコン」と「PC」は同じ物だ、という日本語の知識(扱うアプリケーションによる。同じ物と扱いたくないアプリケーションもあるだろう)</li>
</ul>
<p>の知識が必要になる。</p>
<p>前者が形態素解析と呼ばれる処理。形態素解析器は「『パソコン』というのは名詞である」「『で』というのは助詞である」「『で』は文の最初には現れない」といった知識(辞書)を持っている。この知識を元に文を解析して、日本語としてありそうな切り方を何パターンか出す。その中の一番ありそうな物を結果として返す(それを全文検索エンジンが使う)。</p>
<p>ところで、どのように切るか、というのは、ドメインによって変わる。新聞で使われる言葉、ツイッターで使われる言葉、若者の言葉、年寄りの言葉……。
アプリケーションが扱うドメインが分かっているなら、一般的(?)な切り方でなく、それぞれに適した切り方ができるはずだ。この「それぞれに適した」は、それぞれ用の辞書を使うことで対応する(形態素解析器自体は変えない)。時代とともに検索結果が古びてきたようだと、辞書を新しくする必要も出てくるだろう。</p>
<p>辞書を新しくするということは、インデックスのキーワードの選び方が変わるということだから、その時にはインデックスを作り直す必要がある。</p>
<p>後者、単語(形態素)の同一視は、ノーマライズの前のクエリー展開というフェイズでやっている。そこは辞書を元にクエリーを見て、</p>
<pre><code>「パソコンほしい」 OR 「PCほしい」 OR 「パソコン欲しい」 OR 「PC欲しい」
</code></pre>
<p>みたいなクエリーに変換して検索エンジンに投げている。</p>
<p>「パソコン」と「PC」を同一視するには、形態素解析の結果が欲しいところだがクエリー展開はその前なので、ここは独自に頑張ったりする。その場合は「(二重に同じ処理をすることになのるで)<del>トークナイズ</del><ins>ノーマライズ(勉強会中に発表し、直してもらった)</ins>はしないように」といった仕様上の注意が生まれる。</p>
<p>今回は話題が広くて、また勉強会中に公開したいという制約とでまとめられないがこんな感じだった。あとで追記するかも知れない。</p>
https://diary.kitaitimakoto.net/2016/01/29.html
ノーマライズと形態素解析
2016-01-29T00:00:00Z
2016-01-29T00:00:00Z
<p>ようやく『<a href="https://gihyo.jp/dp/ebook/2015/978-4-7741-7879-0">APIデザインケーススタディ</a>』を読み終えた。デザインの本なのだけど、僕はRubyの様々な組み込み・標準添付ライブラリーの解説書、またはエッセイとして、主に読んだ。楽しい時間で、読み終わって寂しい。</p>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="https://gihyo.jp/dp/ebook/2015/978-4-7741-7879-0" target="_blank"><img src="http://image.gihyo.co.jp/assets/images/gdp/2015/978-4-7741-7879-0.jpg" width="92" height="130" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="https://gihyo.jp/dp/ebook/2015/978-4-7741-7879-0" target="_blank">APIデザインケーススタディ ――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E7%94%B0%E4%B8%AD%E5%93%B2%E8%91%97" target="_blank">田中哲著</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">株式会社技術評論社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2015-12-16</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/13/9784774178790" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>本当は25日に読み終わったのだけど、昨日はSendagaya.rbのことを書いたので、話題を分けるために今日にした。こういう時、<a href="http://www.tdiary.org/">tDiary</a>が恋しくなる。日記の為のソフトウェアだから、一日に複数の話題を書くことが、自然にできる。</p>
<p>この本は「I/O」「ソケット」「プロセス」「時刻」「数、文字列」の五章で構成されている。個人的にこれを</p>
<ul>
<li>OSとの間を埋める(「I/O」、「ソケット」、「プロセス」)</li>
<li>人間社会との間を埋める(「時刻」)</li>
<li>その他(「数、文字列」)</li>
</ul>
<p>と捉えた。</p>
<h2 id="os">OSとの間を埋める</h2>
<p>RubyのI/Oやプロセスなどに関するクラスは、システムコールやglibcの関数を、インターフェイスは模倣して、内部では呼び出すのを原則としてデザインされている。その中でも、そのままglibcからRubyに移植せず、敢えて異なるインターフェイスや動作にしたほうがいい所があったのでそうした、や、ユースケースを考えるとメソッドを追加したほうがいいからそうした、などの事例が紹介されている。それぞれに調査結果、考察、決断が述べられていて、単なるカタログにはなっていなくてケーススタディする内容があり、面白い。</p>
<p>僕はRubyのユーザーではあるけれど、Cは書けない。拡張ライブラリーは使う一方だし、OSのシステムコールなども直接触ることはまずない。<a href="http://docs.ruby-lang.org/ja/2.3.0/method/IO/i/read_nonblock.html">IO#read_nonblock</a>なども、使うことはあってリファレンスマニュアルは読むけど、そこに書いていることで満足して、それでも不明なところは(Rubyプログラムを)動かして確認してから使っていた。この本では、そういった部分のOS側の話なども解説した上で、Rubyではどうしたかと話してくれるので、デザインだけでなく、APIその物の勉強にもなる。「自分が触っている部分の一つ下のレイヤーも理解しておくべきだ」とはよく言われることで、でも中々実践は難しい、その部分を行えるので助かる。</p>
<p>また、余談になるが、ノンブロッキングI/Oという言葉は非常によく聞くし、一応、<a href="http://rubyeventmachine.com/">EventMachine</a>や<a href="https://nodejs.org/en/">Node.js</a>などで使えてはいるつもりだ。でも、下のレイヤーでは、ファイルディスクリプターにブロッキングモードとノンブロッキングモードがあって、ノンブロッキングモードの時にアクセスして読み取り可能でなければこういうエラーになって、という仕組みになっていたことは全く知らなかった。この知識が、直後に読み始めた『<a href="https://gihyo.jp/dp/ebook/2016/978-4-7741-7936-0">nginx実践入門</a>』の最初のほうで活きたので思わず顔がにやけた。</p>
<h2 id="section">人間社会との間を埋める</h2>
<p>第4章は時刻の話。ここはもう、人間側の都合に合わせる話が圧巻。</p>
<p>閏秒があるから将来の日時を正確に表すことは不可能、というのは序の口で、日本にいると、UTCにタイムゾーンオフセットを足せばいいんでしょ? くらいに考えてしまうかも知れないけど夏時間があったらそうはいかない。夏時間、いつから夏時間で、いつまで夏時間か、どのように決まるのか、知っていますか? 僕は知らなかった。「毎年これこれの月のこれこれの曜日」みたいに決まっているのはいい方で、太陰暦に従うとか、その年にならないと政府が決めないとか、ほんと色々ある。夏時間の切り替わりのタイミングでは、存在しない日時や二重に存在する日時が存在するので、日時の扱いは慎重さが必要になってくる。</p>
<h2 id="section-1">その他</h2>
<p>ここは数値関連の色々な話。ここまで読んでいれば、APIデザインのパターンに少しは慣れているので、なるほどなるほどとさくさく進む章だった。</p>
<p>「終わりに」で</p>
<blockquote>
<p>ところで、意識的に使いやすさをデザインするにあたって、具体例と並んで存在して欲しいのは、使いやすさの理論です。理論があれば、個々の具体例がどのような理屈で使いやすさを実現しているか、理解しやすくなりますし、目の前の問題を解決するライブラリをどうデザインするのが良いのか、という見通しを与えてくれるでしょう。さらには、使いにくさという問題を発見することにも役に立つかもしれません。</p>
</blockquote>
<blockquote>
<p>残念なことに、今のところ、そのような理論で満足できるものはなさそうです。</p>
</blockquote>
<p>ということが述べられていた。何気ない文のようだが、最後まで読んだ所で「意識的に使いやすさをデザイン」と言われると、とても重みがある。</p>
https://diary.kitaitimakoto.net/2016/01/26.html
『APIデザインケーススタディ』を読んだ
2016-01-26T00:00:00Z
2016-01-26T00:00:00Z
<p><a href="https://sendagayarb.doorkeeper.jp/events/38134">Sendagaya.rb #134</a>に参加して『メタプログラミングRuby』の読書会をして来た。今日は初参加、それも「最近Ruby始めたばかりで……」とか「プログラミングを始めたばかりで……」という人がすごく多かった。何があったんだろう?</p>
<p>先週に引き続き『<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby 第2版</a>』を読んだ。</p>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Ruby-%E7%AC%AC2%E7%89%88-Paolo-Perrotta/dp/4873117437%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873117437" target="_blank"><img src="http://ecx.images-amazon.com/images/I/5102wwx0VzL._SL160_.jpg" width="117" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Ruby-%E7%AC%AC2%E7%89%88-Paolo-Perrotta/dp/4873117437%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873117437" target="_blank">メタプログラミングRuby 第2版</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/Paolo+Perrotta" target="_blank">Paolo Perrotta</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">オライリージャパン</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2015-10-10</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4873117437" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>「2.2.5 ネームスペースを使う」から15分みんなで黙読して、その後に気になることなどを話した。この「気になること」が盛り上がって、今日は他のことはしなかった。結構知らないことが書いてあって(第1版読んだはずなんだけど殆ど憶えてない……)面白い。</p>
<p>例えばコラムで<a href="http://docs.ruby-lang.org/ja/2.3.0/method/Kernel/m/load.html">load</a>に触れていた。普通<code>load</code>は呼ぶ度にスクリプトが実行されるので、定数定義があると再定義の警告が表示されてしまう。ところが第二引数に<code>true</code>を渡して</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">load</span><span class="p">(</span><span class="s1">'defining-constants.rb'</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span>
</code></pre></div></div>
<p>と呼ぶと、この警告が避けられる。なぜかというと、「第二引数に<code>true</code>を渡すと、無名モジュールが作成され、スクリプトはその中で実行されるから」だと本では説明されている。「なるほどそうなのか、第二引数のこと知らなかった、勉強になったなあ」と、僕はあっさり流していたが、参加者から「無名モジュールってなんですか」っていう声が上がってそう言えば、知らないな、と思った。無名クラス(匿名クラス)はたまに使うのでそこからの類推と、文脈と合わせて勝手にイメージを作って納得していたが、知らない。ということで、デスクトップをプロジェクターで映していた<a href="http://fukajun.org/">fukajun</a>さんがターミナルを出して、動かしてくれる。みんなで「こういうことかな」「じゃあこういう時はこうなるのかな」などと言っていると、それも全部実行してくれる。こうしてみんなで無名モジュール(anonymous module。僕は匿名モジュールと呼びたい)の理解を深めた。(<ins>翻訳の角さんから、無名、匿名についてこんなコメントを頂いた。<a href="https://twitter.com/kdmsnr/status/691839083266641920">https://twitter.com/kdmsnr/status/691839083266641920</a>。うーんなるほど、勉強になる……。</ins>)</p>
<p>その後<code>Class</code>のクラスは? <code>Module</code>のクラスは? <code>Module</code>のスーパークラスは? みたいな話が出ていて、Ruby始めたばかりの人がぴんとこないということだったのでその解説をしたりもした(<a href="https://twitter.com/tkawa">tkawa</a>さんが)。</p>
<p><a href="http://docs.ruby-lang.org/ja/2.3.0/method/Module/i/prepend.html">prepend</a>は第1版にはなかったこともあってかなり盛り上がった。次の記事を見て老害がノスタルジーに浸りながら<code>alias_method_chain</code>の仕組みを解説したりもした。<br />
<a href="http://www.techscore.com/blog/2013/01/22/ruby2-0%E3%81%AEmodule-prepend%E3%81%AF%E5%A6%82%E4%BD%95%E3%81%AB%E3%81%97%E3%81%A6alias_method_chain%E3%82%92%E6%92%B2%E6%BB%85%E3%81%99%E3%82%8B%E3%81%AE%E3%81%8B%EF%BC%81%EF%BC%9F/">Ruby2.0のModule#prependは如何にしてalias_method_chainを撲滅するのか!?</a></p>
<p>本に戻って、文中、<code>prepend</code>に関して、こういう風に動作を説明してくれる。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">M1</span><span class="p">;</span> <span class="k">end</span>
<span class="k">module</span> <span class="nn">M2</span>
<span class="kp">include</span> <span class="no">M1</span>
<span class="k">end</span>
<span class="k">module</span> <span class="nn">M3</span>
<span class="n">prepend</span> <span class="no">M1</span>
<span class="kp">include</span> <span class="no">M2</span>
<span class="k">end</span>
<span class="no">M3</span><span class="p">.</span><span class="nf">ancestors</span> <span class="c1"># => [M1, M3, M2]</span>
</code></pre></div></div>
<p><code>prepend</code>した<code>M1</code>が<code>M3</code>の<strong>前</strong>に差し込まれて、<code>include</code>した<code>M2</code>が<strong>後</strong>に置かれている。一度継承ツリーに入ったモジュールは二度は入らない。説明の通りだ。では、</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">M3</span>
<span class="kp">include</span> <span class="no">M2</span>
<span class="n">prepend</span> <span class="no">M1</span>
<span class="k">end</span>
</code></pre></div></div>
<p>と順番を変えたら、<code>M3.ancestors</code>はどうなるだろうか。実行せずに答えられるだろうか? その答えに、どのくらい確信を持てるだろうか? これも、各々推測を述べた後、「fukajunターミナル」で実行して、回答を得たりした。</p>
<p>こういう、一人で本を読んでいるだけだと見逃しがちなことも、誰かが気付いて声を上げてくれるので読書会中々いいです。ただ(と言っても別に悪いとは思ってないけど)、このやり方は時間は掛かる。ので今日のSendagaya.rbはここで終わった。</p>
<p>次回は</p>
<ul>
<li>『メタプログラミングRuby』引き続き</li>
<li><a href="http://rubyonrails.org/doctrine/">The Rails Doctrine</a>を読んで何か話す</li>
<li><a href="https://blog.heroku.com/archives/2016/1/22/rails-5-beta-upgrade">Upgrading to Rails 5 Beta - The Hard Way</a>を読んでRails 5へのアップグレードの辛さを感じる</li>
</ul>
<p>なんかがいいですかねえ、って話をしていたけど、来週その場で決まることでしょう。</p>
<p>ちなみに、今回は開催前日にDoorkeeperのイベントが作成されたけど、次回分はその日のうちに作成されていたので、もう申し込める:<br />
<a href="https://sendagayarb.doorkeeper.jp/events/38208">https://sendagayarb.doorkeeper.jp/events/38208</a></p>
https://diary.kitaitimakoto.net/2016/01/25.html
『メタプログラミングRuby 第2版』@Sendagaya.rb #134
2016-01-25T00:00:00Z
2016-01-25T00:00:00Z
<p><a href="http://droonga.org/ja/">Droonga</a>は、しばらく<a href="http://droonga.org/ja/install/">マニュアル</a>通りにインストールできない状態だったので、インストーラーを直してプルリクエストを送った。</p>
<ul>
<li><a href="https://github.com/droonga/droonga-engine/pull/39">https://github.com/droonga/droonga-engine/pull/39</a></li>
<li><a href="https://github.com/droonga/droonga-http-server/pull/13">https://github.com/droonga/droonga-http-server/pull/13</a></li>
</ul>
<p>(マージしてもらった後に問題に気付いて追加でちょいちょいパッチを投げてもいる)</p>
<p>マージされたものの、今はまだ修正版がリリースはされていないので、それぞれこういう風に<code>VERSION</code>環境変数を指定してインストールする必要がある。</p>
<pre><code># curl https://raw.githubusercontent.com/droonga/droonga-engine/master/install.sh | \
VERSION=master bash
# curl https://raw.githubusercontent.com/droonga/droonga-http-server/master/install.sh | \
VERSION=master bash
</code></pre>
<p>リリースされたら<code>VERSION=master</code>はなくてもインストールできるようになる。</p>
<p>また、マニュアルでは<code>service</code>コマンドでDroongaの二つのプロセスを管理しているけど、今回のパッチで<a href="http://freedesktop.org/wiki/Software/systemd/">systemd</a>を使うようになったので、今マニュアルを直しているところ。来週中にはパッチを送れるはず。</p>
<p>マニュアル通りに入れられないこと自体は認識していて、ワークアラウンド入りの<a href="http://itamae.kitchen/">Itamae</a>レシピを作って使っていた(<a href="../../2015/12/05.html">DroongaをインストールするItamaeレシピ</a>)。後ろめたさがあった。スキル的には自分で直せるはずなのにそうしていないのは愚か、問題報告すらしていなかった。今回修正して、受け入れてもらって、喉の小骨がようやく取り除けた気分だ。</p>
<p>恥ずかしながらシェルスクリプトに不慣れで、プルリクエストを送ったあとで、色々指摘してもらいながら直していた。ありがとうございました。</p>
<p>systemdのunitファイルにも不安があるので、こうしたほうがいいよというのがあったらぜひ教えてほしい。</p>
<ul>
<li><a href="https://github.com/droonga/droonga-engine/blob/master/install/droonga-engine.service">droonga-engine.service</a></li>
<li><a href="https://github.com/droonga/droonga-http-server/blob/master/install/droonga-http-server.service">droonga-http-server.service</a></li>
</ul>
https://diary.kitaitimakoto.net/2016/01/24.html
Droongaのインストーラーを直した
2016-01-24T00:00:00Z
2016-01-24T00:00:00Z
<p>久し振りに<a href="https://shibuyarb.doorkeeper.jp/events/37064">渋谷.rb[:20160120]</a>に行って来た。初参加の人が半分近くいて、すごいなあと思った。</p>
<p>予め予告のあった<a href="https://docs.google.com/presentation/d/1RTUIJYJUCx_8MRj4hHx7uPbMOQSmCIF3Nobrttd1HfQ/edit">主人がExcel方眼紙に殺されてRubyを書き始めてから5ヶ月が過ぎました</a>っていう発表のほか、みんな何かしら話すことがあって、九時過ぎまで発表が続いていた。中でも<a href="https://github.com/yuku-t">yuku-t</a>さんの発表が印象に残っていて、最終的には<a href="https://github.com/yuku-t/duck_testing">duck_testing</a>っていうRubyGemの紹介と「いい感じにしてほしい」というお願いになったんだけど、このgemを作るに至る経緯の説明がよかった。</p>
<p>基本的には、型でチェックしたいという話。だから、各メソッドにはYARD向けのコメントで引数の型と戻り値の型を書いている。でも当然ながらYARDのコメントをRubyは見てくれないので、<a href="https://github.com/egonSchiele/contracts.ruby">contracts.ruby</a>の導入を検討した。でも、実装方法が危なっかしい(中では、古い人は知っているかも知れない、<a href="https://github.com/michaelfairley/method_decorators">MethodDecorators</a>を使っている)のと、本番でオフにできないので、やめた。YARDのコメントを読み取って自動で型に関するテストを生成し実行する、というgemを作ったとのこと。</p>
<p>YARDはコードの解析に<a href="http://docs.ruby-lang.org/ja/2.3.0/class/Ripper.html">Ripper</a>を使っていて、そのASTにプラグインからアクセスできるので、contarcts.ruby用のプラグインを書けばそこからドキュメントを自動生成できるはずだし、事実そういうYARDプラグインがあるらしい。でも既にYARD向けのコメントをたくさん書いていて、それを全部直して回るのは大変。さらに、YARDでドキュメントコメントを書いているgemは多いので、そこに一行足すだけで自動的にテスまでできるという物は、多くの人に役立つはずだ、という説明がされた。最後の、自分の問題を解決すると、より多くの人の問題も、労力無く解決できるという視点が素晴らしいなと思った。期待したい。</p>
<p>僕は先日の日記に書いた<a href="16.html">『nginx実践入門』をシンタックスハイライトする</a>の内容をデモしつつ、「コードからその言語を判定するところ、今は目視でやって設定をハードコードしていて辛いので、やってくれるgemを知っている人がいたら教えてください」という相談をしてきた。その後の夕食の時とかに聞くと、意外とこれが反響があって嬉しかった。もうちょっと抽象化してgemにしたいなという気持ちになってきた。</p>
<p>あと、渋谷.rbとは関係ないけど日記なので書くと、<a href="https://github.com/KitaitiMakoto/itamae-plugin-resource-security_context">itamae-plugin-resource-security_context</a>というItamaeプラグインをリリースした。今のところ<code>restorecon</code>をするだけだけど、仕事で必要になるに連れてほかのこともできるようにしていきたい。SELinux使っている人がもしいたら、一緒に開発していきましょう……。</p>
https://diary.kitaitimakoto.net/2016/01/20.html
渋谷.rb[:20160120]
2016-01-20T00:00:00Z
2016-01-20T00:00:00Z
<p><a href="https://sendagayarb.doorkeeper.jp/events/37324">Sendagaya.rb #133</a>に行って来た。今日は、前半『<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミングRuby 第2版</a>』を読んで、後半は<a href="https://github.com/rails/rails/tree/master/actioncable">Action Cable</a>を読んだ。</p>
<h2 id="ruby-2">メタプログラミングRuby 第2版</h2>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Ruby-%E7%AC%AC2%E7%89%88-Paolo-Perrotta/dp/4873117437%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873117437" target="_blank"><img src="http://ecx.images-amazon.com/images/I/5102wwx0VzL._SL160_.jpg" width="117" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Ruby-%E7%AC%AC2%E7%89%88-Paolo-Perrotta/dp/4873117437%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873117437" target="_blank">メタプログラミングRuby 第2版</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/Paolo+Perrotta" target="_blank">Paolo Perrotta</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">オライリージャパン</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2015-10-10</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4873117437" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p><a href="http://fukajun.org/">fukajun</a>さんが「本を読むってどうやってやるんですかねえ?」って言ったけど誰も答えを持ち合わせていなかった。十五分みんな黙読し、その後気になったことを話すというスタイルになった。範囲は「2章 月曜日:オブジェクトモデル」の始めから「2.2.4 オブジェクトとクラスのまとめ」まで。みんなRubyを書けるので特に問題がなく、字が汚いとか</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Rake</span>
<span class="k">class</span> <span class="nc">Task</span>
<span class="c1"># ...</span>
</code></pre></div></div>
<p>を</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Rake::Task</span>
<span class="c1"># ...</span>
</code></pre></div></div>
<p>って書いたら<a href="https://houndci.com/">HoundCI</a>に怒られるんだけどなんでだろう? といったことを話していた。</p>
<p>次回は「2.2.5 ネームスペースを使う」から。毎週ちょっとずつは読むとのこと。</p>
<p>また、ここのところ本にシンタックスハイライトを入れるのをやっていたので、この本も帰ったらやろうと思っていたが、始めからハイライトされていた。
<a href="https://gyazo.com/bcd51dd81e50c33c4b8fe5d714ca8887"><img src="https://gyazo.com/bcd51dd81e50c33c4b8fe5d714ca8887.png" alt="『メタプログラミングRuby 第2版』は始めからシンタックスハイライトされている" style="max-width: 80%;" /></a></p>
<h2 id="action-cable">Action Cable</h2>
<p><code>ApplicationCable::Channel</code>のユーザー定義のメソッドのスタックトレースを遡って、どこから呼ばれるのかを見ていった。</p>
<p>fukajunさんがプロジェクターで映しながらエディターを開いてソースを追い掛け、みんなで横からああだこおだと言っていた。読んだのはだいたいこの辺。</p>
<ul>
<li><a href="https://github.com/rails/rails/blob/39f383bad01e52c217c9007b5e9d3b239fe6a808/actioncable/lib/action_cable/connection/subscriptions.rb">action_cable/connection/subscriptions.rb</a></li>
<li><a href="https://github.com/rails/rails/blob/39f383bad01e52c217c9007b5e9d3b239fe6a808/actioncable/lib/action_cable/connection/message_buffer.rb">action_cable/connection/message_buffer.rb</a></li>
<li><a href="https://github.com/rails/rails/blob/39f383bad01e52c217c9007b5e9d3b239fe6a808/actioncable/lib/action_cable/server/worker.rb">action_cable/server/worker.rb</a></li>
</ul>
<p>一度<a href="https://github.com/celluloid/celluloid">Celluloid</a>をやめて<a href="https://github.com/ruby-concurrency/concurrent-ruby">Concurrent Ruby</a>にしたところ(<a href="https://github.com/rails/rails/commit/3b7ccadfc1c8dfec61af898167e1300b17f5cf25">3b7ccad</a>)、それを巻き戻し(<a href="https://github.com/rails/rails/commit/d0393fccffc118a5de37654aa222774b66123393">d0393fc</a>)、更にまた巻き戻す(<a href="https://github.com/rails/rails/commit/01c320001bcce617196270f3d398d48a89a6ea2a">01c3200</a>)ということをしていて、この辺の扱い大変なんだなあという話をした。使いたいのはCelluloidの方であるようだ(<a href="https://github.com/rails/rails/pull/22977">#22977</a>)。</p>
<p>ここを読んだおかげで、<a href="http://necojackarc.hatenablog.com/entry/2015/12/20/043612">Rails5.0.0-beta1のActionCableを使って超簡易チャットを実装してみた</a>にある</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MessagesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">create</span>
<span class="no">ActionCable</span><span class="p">.</span><span class="nf">server</span><span class="p">.</span><span class="nf">broadcast</span> <span class="s2">"messages"</span><span class="p">,</span>
<span class="ss">message: </span><span class="n">params</span><span class="p">[</span><span class="ss">:message</span><span class="p">][</span><span class="ss">:body</span><span class="p">],</span>
<span class="ss">username: </span><span class="n">cookies</span><span class="p">.</span><span class="nf">signed</span><span class="p">[</span><span class="ss">:username</span><span class="p">]</span>
<span class="n">head</span> <span class="ss">:ok</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>みたいなコードを見ても驚かずに「なるほど<code>ActionCable.server.broadcast</code>に渡している<code>"messages"</code>コマンドは別スレッドで実行されるからここはブロックせずに、即座にブラウザーに応答することができるんだな」と考えることができるようになった(でも、僕は気にならないけど、コントローラー内でこういうことするのに違和感を覚える人もいた)。</p>
<h2 id="electronrails-assetsjavascript">electron、rails-assets、JavaScript生態系</h2>
<p>なんかelectronが盛り上がってるらしく、夕食を頂きながら仕組みとかHTMLで作るのどうなの? とか、rails-assetsとか派生してJavaScriptの開発環境って今どんな感じなの? といった話をした。</p>
https://diary.kitaitimakoto.net/2016/01/18.html
Sendagaya.rb #133
2016-01-18T00:00:00Z
2016-01-18T00:00:00Z
<p>『<a href="https://gihyo.jp/dp/ebook/2016/978-4-7741-7936-0">nginx実践入門</a>』を買った。</p>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/nginx%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80-WEB-DB-PRESS-plus-%E4%B9%85%E4%BF%9D/dp/4774178667%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774178667" target="_blank"><img src="http://ecx.images-amazon.com/images/I/511NShYrT8L._SL160_.jpg" width="105" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/nginx%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80-WEB-DB-PRESS-plus-%E4%B9%85%E4%BF%9D/dp/4774178667%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774178667" target="_blank">nginx実践入門 (WEB+DB PRESS plus)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E4%B9%85%E4%BF%9D%E9%81%94%E5%BD%A6" target="_blank">久保達彦</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">技術評論社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2016-01-16</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4774178667" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>早速設定ファイルの所などをシンタックスハイライトした。
<a href="https://gyazo.com/7fa685b4b5ff7ab226c72794b5a89d4b"><img src="https://gyazo.com/7fa685b4b5ff7ab226c72794b5a89d4b.png" alt="Nginxの設定ファイルがシンタックスハイライトされている" style="max-width: 60%;" /></a></p>
<p>以下の手順で再現可能。本のEPUBファイルが<code>path/to/nginx実践入門.epub</code>にあるものとする。</p>
<pre><code>$ gem install epub-parser -v '>= 0.2.4'
$ gem install epub-maker -v 0.0.3
$ gem install rouge rouge-lexers-docker
$ git clone https://gist.github.com/0779a34fd74bae96468f.git rougify-gdp-book
$ cd rougify-gdp-book
$ ruby rougify-gdp-book.rb path/to/nginx実践入門.epub
</code></pre>
<p>EPUBファイルを<strong>上書きする</strong>ので注意すること。</p>
<p>『<a href="https://gihyo.jp/dp/ebook/2015/978-4-7741-7879-0">APIデザインケーススタディ</a>』(<a href="03.html">『APIデザインケーススタディ』を、ソースコードのシンタックスハイライトしながら読む</a>)とか『<a href="https://gihyo.jp/dp/ebook/2015/978-4-7741-7464-8">Dockerエキスパート養成読本</a>』(<a href="http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150705">Dockerエキスパート養成読本を、ソースコードのシンタックスハイライトしながら読む</a>)とか、EPUBファイルに後から手を加えてシンタックスハイライトしているけど、別にこれがそれほどいいことだとは思っていない。こういうことができるように、DRMなしのEPUBを売ってくれている技術評論社には感謝しているが、できれば本を作る時にハイライトを入れてくれるのが一番いいと思っている。</p>
<h2 id="section">追記</h2>
<p>こんなコメントを貰った。</p>
<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">“できれば本を作る時にハイライトを入れてくれるのが一番いいと思っている” モノクロ端末や誤認識を考えると入れるのに二の足を踏むのはあるかも / “『nginx実践入門』をシンタックスハイライトする” <a href="https://t.co/xvneTcDdwf">https://t.co/xvneTcDdwf</a></p>— masayoshi takahashi (@takahashim) <a href="https://twitter.com/takahashim/status/688686509696811009">2016, 1月 17</a></blockquote>
<script async="" src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>なるほど、確かに。色の区別がつきにくい人もいるし、白黒のままのほうがよさそうだ。切り替えられるように作るべきかは、悩ましいところ。</p>
https://diary.kitaitimakoto.net/2016/01/16.html
『nginx実践入門』をシンタックスハイライトする
2016-01-16T00:00:00Z
2016-01-16T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/36432">Groongaで学ぶ全文検索 2016-01-15</a>に参加して来た。今日のテーマは運用、その中でもデータの守り方。</p>
<h2 id="rdbms">RDBMSでのデータの守り方</h2>
<p>Groongaの話の前に、ファイルとRDBMSでのデータの守り方の話をしてもらった。</p>
<p>ファイルにデータを書き込むとデータが永続化する。書き込みの時に、途中で何らかの理由で中断が起きると途中までしか書き込まれない。「Hello」と書き込むつもりだったのに「Hel」までしか書き込まれていない、といったことが起こる。これはデータが守られていない状態だと言える。不測の事態のせいでおかしなデータを持つことになってしまっている。</p>
<p>RDBMSでは、こういった「おかしなデータ」が存在しないように、トランザクションを使うようになっている。トランザクションを使うと、場合によってはデータが書き込まれていないことがあるが、途中まで書き込まれているというおかしな状態になるのは防ぐことができる。</p>
<p>RDBMSがこうして頑張っていても、ストレージデバイスが壊れるとデータは失われてしまう。これを防ぐのは「運用」の仕事になる。</p>
<p>データ消失を防ぐためには、何らかの方法でコピーを取る。コピーがあればあるだけ、それら全部が同時に壊れる可能性は低くなるので、データは守られる。ただ、お金が掛かったり帯域を使用したりするというトレードオフがある。</p>
<h2 id="groonga">Groongaでのデータの守り方</h2>
<p>ここまでがデータを守る運用の一般的な話で、ここから全文検索エンジン、特にGroonga族の話。</p>
<p>全文検索エンジンはトランザクションに対応している物は少なく、Groongaも対応していない。こうした時におかしなデータという状態を防ぐには大きく分けて二つの方法がある。</p>
<p>一つはデータベースのデータのバックアップを取っておいて、データ損失が起こったら、バックアップ時点へ巻き戻す方法。この場合、バックアップ時点から障害時点までの間に新しく作られたデータは失われ、更新されたデータは更新前の物に戻る。</p>
<p>二つ目は、Groongaでデータがおかしな状態になったり失われたりするのを防ぐのは諦めて、マスターデータの方が失われないよう仕組みを整える方法。マスターデータがあれば、いつでもGroongaのインデックスを復旧できる。Groongaの運用では、こちらのほうが現実的。マスターデータの守り方は色々あるので、データの性質やシステムの条件などで適切な物を選ぶ。RDBMS上のデータをマスターとしたり、ファイルとして保存したり、クラウド上のデータストアに置いたり。</p>
<h2 id="section">復旧に掛かる時間</h2>
<p>復旧することでデータを元に戻せるが、これに時間が掛かると、ダウンタイムがそれだけ長くなってしまう。上の二つ目、マスターデータを頑張って守る方法は、Groongaの復旧では一から全てのデータを入れ直すので、最も時間がかかる。</p>
<p>一つ目の方法の時間の掛かり方は、バックアップ時点から障害時点までの間のデータの扱い次第。そこのデータを諦めるなら、バックアップが(コピーなどするだけで)そのまま使えるので復旧は速い。バックアップ時点から障害時点までは、コピーではなくて差分バックアップを取っておくようにする方法もある。この場合は、バックアップ時点までの復旧は速く、そこから差分バックアップの内容を適用する部分では時間が掛かる。</p>
<p>また、一つ目の方法、コピーを取る方法のバリエーションとして、レプリケーションもある。レプリケーションは概ねリアルタイムに全データをバックアップ取れるが、デザイン上の問題やネットワーク遅延、マスターとレプリカ間の性能の違いなどで遅延はある。</p>
<h2 id="groonga-1">Groongaでのバックアップ</h2>
<p>Groongaにはトランザクションがないので、書き込み中に処理が止まるとデータがおかしなことになってしまう(ことがある)。マスターデータを用意して復旧に備えるのが現実的な運用になる。この時、前述の通り復旧に時間が掛かってしまうことになるが、普段サービスしている時は一つ一つ順にデータを書き込んでいるような物でも、まとめて一度に書き込めるモードがあって、これを使うとより速く復旧できる。また、そもそもGroongaは書き込み性能が高いので、RDBMSに比べると復旧は速い。</p>
<p>Groonga族のMroonga、PGroongaでのバックアップは、それぞれのRDBMSの仕組みに乗るのがよい。それぞれトランザクションやレプリケーションがある。</p>
<p>また、MySQLの場合、InnoDBのレプリケーション先としてMroongaを選ぶことができる。こうすると、(データが守られやすい)InnoDBをマスターデータとして考えることができる。</p>
<p>PostgreSQLにもレプリケーションの仕組みはあるが、MySQLのレプリケーションの仕組みとはだいぶ違う。MySQLではINSERTなどの命令をレプリケーションに流すので、その命令をGroongaの物として読み替えることができた。PostgreSQLはファイルパスやファイル内のバイトオフセットなど物理的な位置を流してレプリケーションに使うので、この情報をGroongaのインデックス更新のために読み替えることができない。そこで、復旧の際には、PostgreSQLにREINDEXを書けてGroongaのインデックスを作り直すことになる。</p>
<h2 id="section-1">レプリケーションではまりがちな罠</h2>
<p>また、全文検索エンジンに限らないがレプリケーションの運用について大事なことは、システムのキャパシティを考える時にレプリカを含んではいけないということだ。マスターとレプリカを含めた台数でちょうどさばけているようなシステムは、レプリカが一台落ちただけで、システム全体が検索をさばき切れなくなって、落ちたりする。</p>
<p>レプリカは遊んでいるように見えるのでつい使いたくなるが使ってはいけない。正確には、レプリカを入れてちょうど検索を捌けるというようなプランニング、運用をしてはいけない。</p>
<h2 id="section-2">その他</h2>
<p>気になったので聞いてみた。マスターDBでSSDを使うのはありなのか? 実際SSDで運用している人がいた。のでありなんだろうなあ。</p>
<p>Groongaのデータをコピーやdumpコマンドによってバックアップ取る際、中途半端な状態のコピーになってしまうことはないのか? ある。のでシステムから切り離してバックアップするとか、バックアップ専用にテーブルを作ってバックアップ取ったら消すとか、工夫して運用する必要がある。</p>
https://diary.kitaitimakoto.net/2016/01/15.html
Groongaの運用(データの守り方)
2016-01-15T00:00:00Z
2016-01-15T00:00:00Z
<p>昨日(<a href="02.html">EPUB書籍に正誤表を反映する(Rubyスクリプトで)、またはEPUBのパッチプログラムの試み</a>)に引き続き「ちゃんと正誤表を公表してくれる著者と、DRMをかけない素のEPUBファイルを配信してくれる出版社があればこそ」シリーズの第二弾(第三弾は多分無い)。</p>
<p>以前<a href="http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150705">Dockerエキスパート養成読本を、ソースコードのシンタックスハイライトしながら読む</a>という日記を書いたが、その時のスクリプトを修正して、『APIデザインケーススタディ』にも対応させた。</p>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/API%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%B9%E3%82%BF%E3%83%87%E3%82%A3-%7ERuby%E3%81%AE%E5%AE%9F%E4%BE%8B%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B6%E3%80%82%E5%95%8F%E9%A1%8C%E3%81%AB%E5%8D%B3%E3%81%97%E3%81%9F%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%A8%E6%99%AE%E9%81%8D%E3%81%AE%E8%80%83%E3%81%88%E6%96%B9-WEB-PRESS-plus/dp/4774178020%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774178020" target="_blank"><img src="http://ecx.images-amazon.com/images/I/514Ve4nSUZL._SL160_.jpg" width="105" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/API%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%B9%E3%82%BF%E3%83%87%E3%82%A3-%7ERuby%E3%81%AE%E5%AE%9F%E4%BE%8B%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B6%E3%80%82%E5%95%8F%E9%A1%8C%E3%81%AB%E5%8D%B3%E3%81%97%E3%81%9F%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%A8%E6%99%AE%E9%81%8D%E3%81%AE%E8%80%83%E3%81%88%E6%96%B9-WEB-PRESS-plus/dp/4774178020%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774178020" target="_blank">APIデザインケーススタディ ~Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方 (WEB+DB PRESS plus)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E7%94%B0%E4%B8%AD%E5%93%B2" target="_blank">田中哲</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">技術評論社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2015-12-16</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4774178020" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>元々の本は(PDF版から類推するに)ソースコードの所も白黒のようだけど、こうしてハイライトして読むことができるようになる。</p>
<figure>
<a href="https://gyazo.com/ed5d038aace704a0fcea8313dddf3fb9" target="gyazo"><img src="//gyazo.com/ed5d038aace704a0fcea8313dddf3fb9.png" alt="Rubyのコードがシンタックスハイライトされている" style="max-width: 50%;" /></a><a href="https://gyazo.com/5bf04bb00b3537f488e4811ceab3f826" target="gyazo"><img src="//gyazo.com/5bf04bb00b3537f488e4811ceab3f826.png" alt="PythonやPerlも含めソースコードがシンタックスハイライトされている" style="max-width: 50%;" /></a>
</figure>
<p>スクリプトは前と同じ所に置いてある:<br />
<a href="https://gist.github.com/KitaitiMakoto/0779a34fd74bae96468f">https://gist.github.com/KitaitiMakoto/0779a34fd74bae96468f</a></p>
<p>クローンなりダウンロードなりして</p>
<pre><code>$ ruby rougify-gdp-book.rb path/to/api-design.epub
</code></pre>
<p>と実行すればよい。</p>
<p>これで、ようやく本を読む準備が整った。</p>
<blockquote>
<p>ともあれ、<strong>こんなことができるのも、ちゃんと正誤表を公表してくれる著者と、DRMをかけない素のEPUBファイルを配信してくれる出版社があればこそ</strong>。感謝したい。</p>
</blockquote>
<p>(<a href="http://sho.tdiary.net/20151222.html"><cite>EPUB書籍に正誤表を反映する</cite></a>)</p>
<p>である。</p>
<hr />
<p>スクリプトの修正にあたって、プログラミング言語の推測に苦労した(ハイライトに使っている<a href="http://rouge.jneen.net/">Rouge</a>はshebangを見るくらいしかしてくれない)。主な方法は、「そのソースコードが含まれる節の見出し(<code>h2</code>や<code>h3</code>)と言語の対応表を作る」ということになった。「見出しにシステムコールという語が含まれていればCだろう」という具合である。</p>
<p>これも中々うまい法則を見付けられず、結局一つ一つの見出しと<code>code</code>要素を見て手作業で対応表を作った。途中、「こんなのは人間の仕事ではない!」と思って<a href="https://github.com/github/linguist">github-linguist</a>の使用も検討したが、Gitリポジトリー全体でなく個別のテキストに対して使う方法がすぐに分からなかったのでやめた。</p>
<p>技評の方で<code><code></code>要素の<code>class</code>属性や<code>data-*</code>属性で言語名を書いておいてくれると、一番楽なんだけどなあ。</p>
<hr />
<p>ところで、確認中に、EPUB版の索引が全然機能しないことに気付いたんだけど、これも本文へのリンクにするスクリプトを配ったら喜ばれるものだろうか……。</p>
https://diary.kitaitimakoto.net/2016/01/03.html
『APIデザインケーススタディ』を、ソースコードのシンタックスハイライトしながら読む
2016-01-03T00:00:00Z
2016-01-03T00:00:00Z
<p>田中哲さんの『<a href="https://gihyo.jp/dp/ebook/2015/978-4-7741-7879-0">APIデザインケーススタディ</a>』を買ったので、前『<a href="http://gihyo.jp/book/2015/978-4-7741-7441-9">Dockerエキスパート養成読本</a>』でやったように(<a href="http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150705">Dockerエキスパート養成読本を、ソースコードのシンタックスハイライトしながら読む</a>)ソースコードの部分をシンタックスハイライトしようとしたところで、ただただしさんの「<a href="http://sho.tdiary.net/20151222.html">EPUB書籍に正誤表を反映する</a>」という日記を読んだ。本の<a href="https://github.com/akr/api-design-case-study-book">正誤表</a>を見ながらEPUBファイルの中身を直接書き換えることで、誤りを正した状態で読み始められるようにする、という内容だ。</p>
<p>これは素晴らしい、ぜひ真似しよう、と思って、スクリプトを書いた:<br />
<a href="https://gist.github.com/KitaitiMakoto/7b2286b61a0bafcc5926">https://gist.github.com/KitaitiMakoto/7b2286b61a0bafcc5926</a></p>
<p>必要なのは</p>
<ul>
<li>『<a href="https://gihyo.jp/dp/ebook/2015/978-4-7741-7879-0">APIデザインケーススタディ</a>』のEPUBファイル(<code>path/to/book.epub</code>にあることにする)</li>
<li>Ruby</li>
<li>幾つかのRubyGem:<code>$ gem install nokogiri-xml-range epub-parser epub-maker</code></li>
<li>Gistにある<code>reflect-errata-api-design.rb</code>のファイル</li>
</ul>
<p>で、全て揃ったら</p>
<pre><code>$ ruby reflect-errata-api-design.rb path/to/book.epub
</code></pre>
<p>と実行すると正誤表を反映してくれる。</p>
<figure>
<a href="https://gyazo.com/10bc38f6f2acb88d2217b3d36c1c4e62" target="gyazo"><img alt="正誤表適用前" src="//gyazo.com/10bc38f6f2acb88d2217b3d36c1c4e62.png" style="width: 50%;" /></a><a href="https://gyazo.com/5bd09585adc98a282ea30b000cc8e101" target="gyazo"><img alt="正誤表適用前" src="//gyazo.com/5bd09585adc98a282ea30b000cc8e101.png" style="width: 50%;" /></a>
<figcaption>「w<strong style="color: red;">ir</strong>te」だった所が「w<strong style="color: red;">ri</strong>te」と修正されている</figcaption>
</figure>
<p>大掛かりである。上記の手順だけで充分大掛かりなのに、このスクリプトを書くには数時間を要している。今回程度の数、内容なら、たださんの日記にあるように、エディターを使って手作業で反映させるのが一番手間がないだろう。</p>
<p>ちなみに</p>
<blockquote>
<p>正誤表はページ数指定なので、HTMLファイルを特定するのにPDF版を参照してページ数から章番号を突き止めるしかないのがやや面倒</p>
</blockquote>
<p>とのことで、EPUBでの対応箇所を探す多分一番簡単な方法は</p>
<ol>
<li>EPUBファイルを展開する<br />
<code>$ unzip path/to/book.epub -d api-design</code></li>
<li><code>grep</code>やThe Silver Searcher(<code>ag</code>)、The Platinum Searcher(<code>pt</code>)で展開したディレクトリーを探す<br />
<code>$ ag wirte api-design</code><br />
<code>$ ag をを api-design</code><br />
:</li>
</ol>
<p>ではなかろうかと思う(検索機能付きのパソコン向けEPUBリーダーってある?)。</p>
<h2 id="gem">使っているgemの紹介</h2>
<p>さてこのスクリプト、これまで色々準備してたことのプチ決算な趣があるので、少し自慢話にお付き合い願いたい。動かすのに必要なgemを三つ挙げたが、全て僕が作ったgemで、こんなこともあろうかと準備してきた物々なのである。</p>
<h3 id="epub-parsercfi">EPUB ParserのCFI実装</h3>
<p>前からずっと<a href="http://www.rubydoc.info/gems/epub-parser/0.2.4/file/docs/Home.markdown">EPUB Parser</a>という、EPUBファイルの中身を調べるgemを作っていた。このgemが扱っているEPUB 3仕様には<a href="http://www.idpf.org/epub/linking/cfi/epub-cfi.html">EPUB CFI</a>という補足的な仕様がある。「本の中のある一点(一文字)」や「ある場所からある場所まで」といった範囲を指定するための記法を定義した仕様だ。</p>
<pre><code>epubcfi(/6/36!/4/2/16/5,:25,:27)
</code></pre>
<p>のようなちょっと目を疑う読みにくさの記法なのでずっと敬遠してきたのだが、ちょうど今回の「EPUBパッチ」のような時に使えるかと、数か月前に重い腰を上げて実装したのだった。</p>
<p>正確にはパッチで終わるのでなく、差分アップデートをやってみたいと思っている。対象もEPUBじゃなくてDOMにしたい、つまりウェブページも対象にしたい。ただ、まずは(要素の省略などが許されているHTMLでなく)必ずXHTMLを使うことになっているEPUBからと思っているし、EPUBでこれができると、Kindleのようなプラットフォームで、本につけたハイライトやメモ書きを保持したまま本の内容をアップデートできるはずだ。電子書籍のいいところに、配信側が気軽にアップデートできることがあるが、そのたびにメモ書きが消えてしまうのは避けたい。また、まんがなんか特にそうだが、不要な所も含めた本全体をアップデートしていると転送料ももったいないしユーザーも長く待たされる。差分アップデートならこれが避けられる。</p>
<p>そんな思惑で実装していたCFIの機能が、今回役に立った。Gistにあるスクリプトの</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">ERRATA</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span><span class="ss">target: </span><span class="s1">'/6/36!/4/2/16/5,:25,:27'</span><span class="p">,</span> <span class="ss">operation: :replace</span><span class="p">,</span> <span class="ss">replace: </span><span class="s1">'ri'</span><span class="p">},</span>
<span class="p">{</span><span class="ss">target: </span><span class="s1">'/6/36!/4/2/18/7,:33,:35'</span><span class="p">,</span> <span class="ss">operation: :replace</span><span class="p">,</span> <span class="ss">replace: </span><span class="s1">'ri'</span><span class="p">},</span>
<span class="p">{</span><span class="ss">target: </span><span class="s1">'/6/46!/4/2/70/6/1,:0,:4'</span><span class="p">,</span> <span class="ss">operation: :replace</span><span class="p">,</span> <span class="ss">replace: </span><span class="s1">'send'</span><span class="p">},</span>
<span class="p">{</span><span class="ss">target: </span><span class="s1">'/6/52!/4/2/28/1,:61,:62'</span><span class="p">,</span> <span class="ss">operation: :remove</span><span class="p">},</span>
<span class="p">{</span><span class="ss">target: </span><span class="s1">'/6/68!/4/2/44/1:267'</span><span class="p">,</span> <span class="ss">operation: :add</span><span class="p">,</span> <span class="ss">add: </span><span class="s1">'が'</span><span class="p">},</span>
<span class="p">{</span><span class="ss">target: </span><span class="s1">'/6/98!/4/2/12/9:7'</span><span class="p">,</span> <span class="ss">operation: :add</span><span class="p">,</span> <span class="ss">add: </span><span class="s1">'*10'</span><span class="p">},</span>
<span class="p">{</span><span class="ss">target: </span><span class="s1">'/6/118!/4/2/50/3,:45,:46'</span><span class="p">,</span> <span class="ss">operation: :remove</span><span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<p>という定数で使っている。<code>target</code>プロパティのところがそれだ。</p>
<p>カンマで区切られているやつが「範囲」で、区切られていないのが「一点」を表す。削除や差し替えは「どこを」という情報が必要だから範囲を使っているし、文字の追加は不要なので一点を使っている。</p>
<p>このCFI、おもしろい特徴があって、CFI同士、順番をつけることができるのだ。まあ、(始めから終わりまで一次元に続く)本の一点や範囲を示しているんだから、数直線上の点や範囲と同じで、考えてみれば順序が付くのは当たり前なのだが。この順番を、<strong>XHTML文書その物は参照せずに</strong>決められるところがおもしろい。他に何も見ないでも、</p>
<pre><code>epubcfi(/6/36!/4/2/16/5:25)
</code></pre>
<p>と</p>
<pre><code>epubcfi(/6/36!/4/2/18/7:33)
</code></pre>
<p>なら前者(<code>epubcfi(/6/36!/4/2/16/5:25)</code>)のほうが「先」にあるということが分かる。正誤表適用前に、適用箇所が後ろの方から前の方に並ぶように、正誤表を並び替えているのだが、その時に、この順番の機能を使った(Rubyの<code>sort_by</code>メソッドのブロックから返している)。なぜ後ろから前なのかと言うと、もし前からやってしまうと、適用の結果DOMツリーの構造が変わって、その後の操作の適用対象がずれてしまうことがあるからだ(DOMのNodeSetの中から複数ノードを消すときなんかと同じ)。</p>
<p>これは同じくDOMツリー上の場所を示すのによく使われるCSSセレクターやXPathにはない特徴で、パッチ適用箇所の表現に(渋々ながら)EPUB CFIを採用した理由になっている(CSSセレクターやXPathでも一定の制限を掛けてみんな守るようにすれば順番付けはできる)。</p>
<p>余談だけど、<code>insert</code>じゃなくて<code>add</code>、<code>delete</code>じゃなくて<code>remove</code>といった用語は<a href="https://tools.ietf.org/html/rfc5261">XML Patch</a>と<a href="https://tools.ietf.org/html/rfc6902">JSON Patch</a>(<a href="http://www.hcn.zaq.ne.jp/___/WEB/RFC6902-ja.html">日本語訳</a>)から拝借した。</p>
<h3 id="nokogirixmlrange">Nokogiri::XML::Range</h3>
<p><a href="http://www.rubydoc.info/gems/nokogiri-xml-range">Nokogiri::XML::Range</a>については以前にも書いた(<a href="http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150907">NokogiriでHTML(XML)内の範囲を操作するgem作った</a>)。DOMツリー上の範囲を扱うgemだ。ウェブブラウザーではJavaScript向けのAPIである<a href="https://developer.mozilla.org/ja/docs/Web/API/range">Rangeオブジェクト</a>として見ることができる。</p>
<p>EPUB CFIで正誤表適用箇所を指定できたとしても、そこに対して操作ができなければまるで意味がない。CFIから<a href="http://www.nokogiri.org/">Nokogiri</a> gemで表現されたDOMツリー上の範囲へ変換し、それに対して追加・削除・差し替えを実施するのにNokogiri::XML::Rangeを使っている(長さ0の範囲に対する操作として、追加にも範囲を使っている)。</p>
<p>こう使っている。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">range</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Range</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"CPUB CFIから変換した「始点」と「終点」の情報"</span><span class="p">)</span>
<span class="c1">## 追加操作 ##</span>
<span class="c1"># rangeは追加するべき場所を示している</span>
<span class="n">text_to_insert</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Text</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"追加する文字列"</span><span class="p">,</span> <span class="s2">"ドキュメントオブジェクト"</span><span class="p">)</span>
<span class="n">range</span><span class="p">.</span><span class="nf">insert_node</span> <span class="n">text_to_insert</span>
<span class="c1">## 削除操作 ##</span>
<span class="c1"># rangeは削除するべき範囲(「をを」の「を」一つとか)を示している</span>
<span class="n">range</span><span class="p">.</span><span class="nf">delete_contents</span>
<span class="c1">## 差し替え操作 ##</span>
<span class="c1"># rangeは差し替えるべき範囲(誤字「wirte」の「ir」とか)を示している</span>
<span class="n">range</span><span class="p">.</span><span class="nf">delete_contents</span>
<span class="n">text_to_replace</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Text</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"差し替え後の文字列"</span><span class="p">,</span> <span class="s2">"ドキュメントオブジェクト"</span><span class="p">)</span>
<span class="n">range</span><span class="p">.</span><span class="nf">insert_node</span> <span class="n">text_to_replace</span>
</code></pre></div></div>
<p><code>delete_contents</code>や<code>insert_contents</code>がやっていることは実装すると結構めんどうなのだけど、きちんと仕様の存在する挙動なので、gemに切り出しておけば安心して使える。</p>
<p>これも正に「差分アップデートやるなら必要になるはずだな」と思って作ったgemなので、狙い通りに役立って嬉しい。</p>
<h3 id="epub-maker">EPUB Maker</h3>
<p><a href="http://www.rubydoc.info/gems/epub-maker">EPUB Maker</a>は<a href="http://www.rubydoc.info/gems/epub-parser/0.2.4/file/docs/Home.markdown">EPUB Parser</a>の拡張で、その名の通りEPUBを作成するためのgem……というのは表の顔で、これを作った一番の動機はEPUBのインプレース編集にあった(EPUBを作るなら<a href="https://github.com/kmuto/review">Re:VIEW</a>や<a href="https://github.com/skoji/gepub">gepub</a>など他のgemのほうがいいと思う)。</p>
<p>冒頭でちょっと触れたDocker本のシンタックスハイライトでも使っているが、EPUBファイルの中身を、Nokogiriを使ったDOM操作などで直接書き換えることができる。</p>
<p><a href="http://www.rubydoc.info/gems/nokogiri-xml-range">Nokogiri::XML::Range</a>で正誤表を適用したあとは、単に<a href="http://www.rubydoc.info/gems/epub-maker">EPUB Maker</a>の保存用メソッドを呼べば、それでEPUBファイルに適用される。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">item</span><span class="p">.</span><span class="nf">content</span> <span class="o">=</span> <span class="n">document</span><span class="p">.</span><span class="nf">to_xml</span>
<span class="n">item</span><span class="p">.</span><span class="nf">save</span>
</code></pre></div></div>
<p>べんり。</p>
<p>こうして、兼ねてから用意しておいたgemの組み合わせで、今回のパッチプログラムは比較的すんなり書くことができた。気持ちがいい(とは言え、今回の本に特化した方法ならもっとずっと簡単に書ける。<code>unzip</code>、<code>zip</code>、<code>sed</code>くらいで充分だ。明らかにオーバーエンジニアリング)。</p>
<p>今回足りなくて自分で書かないといけなかった汎用パーツは「EPUB CFIからNokgiri::XML::Rangeに変換する」という処理だったので、これは一般化して<a href="http://www.rubydoc.info/gems/epub-parser/0.2.4/file/docs/Home.markdown">EPUB Parser</a>に入れておきたい。</p>
<h2 id="epub">EPUBパッチの試み</h2>
<p>こうして、ある程度アドホックに、ある程度一般的にEPUBのパッチプログラムを書いてみた。書いてみて一番大変だったのは、正誤表のEPUB CFIを作るところだった。</p>
<p>本の中から、適用対象のXHTMLファイルを探して、その中の適用箇所を探すまでは簡単だ(<code>grep</code>などでできる)。でもその場所を表現するための要素の順番などを数えるのが面倒くさい。そして間違える。</p>
<p>あと、複数の操作を一つのプログラムで行うときの順番の扱いは、色んなケースを集めて検討する必要があると感じた。今回は正誤表を逆順に並べて適用していったけど、前の方から順番でも、そうと決まっていれば別にいい。ある処理でDOM構造が変わるとしても、「ずれた」後のDOMツリーに対してその後の操作のCFIが書かれていればいいからだ。</p>
<p>もう少し引いた視点で、パッチの適用対象のバージョンや順番も、取り決めを作ってみんなに周知する必要があると感じた。今回のパッチプログラムの後に正誤表が追加されたとする。すると、</p>
<ul>
<li>gihyo.jpからダウンロードしたファイルには、今回分と追加分がまとまったパッチを適用したい</li>
<li>今回のパッチを適用して楽しんでいたファイルには、追加分だけ適用したい</li>
</ul>
<p>ということになる。この辺、「二つをまとめたパッチ」を作るかどうか、作るにはどういう手順で作るか、それとも必ず一つずつ順番に適用することにするか(「久し振りに開いた本」は、「パッチの適用待ち」の時間が非常に長くなるかも知れない)、決めないといけない。</p>
<p>また、サードパーティ製のパッチについても検討できると素晴らしい。識者による注釈や、出版社を通さない作者によるコメンタリーなど(小説へのコメンタリーは、吉野茉莉さんがやっていた)用意して配布できるといい。そうした物は「どのバージョンのパッチ適用後なら適用していいか」「それより後のパッチも適用した後だった場合、どうしたらいいのか」といった難しい検討が必要になる。</p>
<p>差分アップデートも同様だけど、「(本文を参照しない)CFIだけでの演算」でいろいろ解決できると便利なのだけど、そういったことはできるのだろうか……(というか、これができるかどうか見てみたいので差分アップデートをやりたいのだ)。</p>
<h2 id="epub-cfi">EPUB CFIについて思うこと</h2>
<p>EPUB CFI、あまり好きではないのだけど、今回のようなことをやるには、順番が付くという性質が役に立った。</p>
<p>今回分かった難点というか、改善点になるのかな、は、『APIデザインケーススタディ』では(XHTMLの)<code>id</code>属性を全然使っていないこともあって、ぱっと見、どのトピックに対する操作なのか全然分からない(<code>id</code>がある場合はその値がCFIに現れる仕様になっている)。<code>id</code>に限らず、<code>class</code>など色んな属性についてもCFI表現に出せるようになってるといいのかなあ。乱用される危険も出るが。</p>
<h2 id="section">差分アップデートの仕組みに足りていない物</h2>
<p>今回一番大変だったこととしてCFIの作成を挙げたが、そこが、差分アップデートの仕組みに足りていない。</p>
<p>(iBooks Authorなど)オーサリングツールで何か操作をして保存したりアイコンをタップすると、自動でパッチを書き出すようになっているとすごく便利だが、僕にはGUIは理解が追い付かない……。</p>
<p>別の方法としてEPUB用の<code>diff</code>コマンドを作るというのがある、と言うか、目指している。旧EPUB、新EPUBを並べて差分を計算し、パッチの形で書き出してくれるツールだ。これでネックになるのはDOMツリーの差分計算だ。調べたところ「NP困難」と呼ばれる類の問題らしく、一般的に解決するのは非常に難しいらしい。でも、DOMの差分が作れると、ウェブ開発者一般にもとても役立つと思うので、何か、妥当でうまく利く制約があるといい。アルゴリズムとしてはBULDアルゴリズムというのが速いらしい。C++の実装はあるけれど、僕には敷居が高いので、これのポーティングを目的として今Goを勉強している(速い言語がいい)。仮想DOM方面から何か出てきたりしないかな。</p>
<h2 id="section-1">終わりに</h2>
<p>なんだか、実現できているのは小さなことだし、書いているコードは少ないのに、長く話してしまった。お恥ずかしい。</p>
<p>最後に大事なことを一つ、たださんと同じく声を大にしてこう言いたい。</p>
<blockquote>
<p>ともあれ、<strong>こんなことができるのも、ちゃんと正誤表を公表してくれる著者と、DRMをかけない素のEPUBファイルを配信してくれる出版社があればこそ</strong>。感謝したい。</p>
</blockquote>
<h2 id="section-2">追記</h2>
<p>なんか間違えてた。上で何度か「差分アップデートをやりたい」と言っているが、差分アップデートは、正に今回やったこれだ(パッチ作成の部分は今は人間がやってるのでそこは将来の話ではある)。やりたいけど遠いのは、「EPUBの差分アップデート時に、ブックマークやハイライト、メモ書きなども同時にアップデートする(EPUBアップデートによって場所が動いても追従する)」ということだった。</p>
https://diary.kitaitimakoto.net/2016/01/02.html
EPUB書籍に正誤表を反映する(Rubyスクリプトで)、またはEPUBのパッチプログラムの試み
2016-01-02T00:00:00Z
2016-01-02T00:00:00Z
<p>今日はちょっと思い出話でも。</p>
<p><a href="https://sapporoonga.doorkeeper.jp/events/36441">Groongaもくもく会@札幌 2015-12-30</a>に参加して来た。帰札した翌日から熱を出して寝込んでいたのだが、直前で治って本当によかった。</p>
<p>もくもく会では<a href="https://github.com/droonga/droonga-http-server/blob/master/install.sh">Droonga HTTP Serverのインストールスクリプト</a>の修正を試みていた。進捗はこんな感じ:<a href="https://github.com/droonga/droonga-http-server/compare/master...KitaitiMakoto:centos-systemd">https://github.com/droonga/droonga-http-server/compare/master…KitaitiMakoto:centos-systemd</a></p>
<p>その後は<a href="https://atnd.org/events/73363">忘年会@Sinatra札幌&Sapporoonga 2015-12-30</a>。そこで話題になった一つに、Railsの躓きポイントの話があった。既にうろ覚えだがこんな感じだったと思う。</p>
<p>ウェブアプリケーションを作ったことがない状態でRailsに入門すると、どこで何が起こっているか分からない。scaffoldしてアプリケーションが動くようにはできる。でも何がどうなっているか分からない。その後Sinatraをちょっと勉強して、</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">get</span> <span class="s1">'/'</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="n">get</span> <span class="s1">'/entries'</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="n">get</span> <span class="s1">'/entries/1'</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span>
</code></pre></div></div>
<p>みたいなのに触れてみて初めて、「Railsはroutesをまず見るべきなんだ」と分かった、という話。そこから、Rails(のようなフルスタックのフレームワーク)に触れる前に、ウェブの基本的な仕組みを学ぶべきだ、という話になった。その場には<a href="https://www.djangoproject.com/">Django</a>をやっている人もいて、やり始めはやはり同じような分からなさを感じていたらしい。</p>
<p>僕は、自分のRailsの覚え方は普通ではないし人に勧めるような物ではないと感じていたのだけど、この話を聞いていると意外とよかったのかも知れないと思えてきた。僕がRailsを覚えたのは、Railsの本でも勉強会でもなくて(当時はプログラミングの話をする相手すらいなかった)、『<a href="http://www.oreilly.co.jp/books/9784873113531/">RESTful Webサービス</a>』だった(今はPDF版もあるようだ)。</p>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/RESTful-Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9-Leonard-Richardson/dp/4873113539%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873113539" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51Su2Ger2sL._SL160_.jpg" width="116" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/RESTful-Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9-Leonard-Richardson/dp/4873113539%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4873113539" target="_blank">RESTful Webサービス</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/Leonard+Richardson" target="_blank">Leonard Richardson</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">オライリー・ジャパン</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2007-12-21</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4873113539" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>この本は、勿論Railsの入門書ではない。書名の通り、RESTful Webサービスの解説書だ。前半で、ウェブとは何か、HTTPとは、URIとは、アドレス可能性とは、<abbr title="Representational State Transfer">REST</abbr>とは、といった基礎的な考え方と、実例として、Google Mapsのような地図サービスのサーバーサイドのリソース設計が紹介される。今実家にいて手元に実物が無いので記憶頼りだが(こういう時電子本だったら……)、どういうXMLにするべきか(当時はWeb APIはXMLを返すのが普通だった。そしてWeb APIでなくWebサービスと呼ばれていた)、どういうURIにするべきか、地図の拡大・縮小はURIとしてどのように表現するといいか、といったことが紹介される。</p>
<p>そうしてみっちりと理論的なことを叩きこまれた後で後半、ようやくサンプルウェブアプリケーションとして、<a href="https://delicious.com/">del.icio.us</a>クローンのブックマークサービスを作ることになる。この時に採用されたフレームワークが、<a href="http://rubyonrails.org/">Ruby on Rails</a>だったのだ。Railsのバージョンは2で、ようやくRESTfulルーティングが入ったような頃だったと思う。Rackはまだ入っていなかった。</p>
<p>僕はこれを読みながら、HTTPメソッドとRailsのアクション名との対応表を何度も見返しながら、サンプルアプリケーションを写経することでRailsを覚えていった(Railsアプリケーションの作り方の簡単な解説もあった)。上で言っていた「ウェブアプリケーションの基本的な仕組み」を散々解説された後でRailsに触れることになったし、しかもこの本の順番だとURI設計をし、<code>config/routes.rb</code>に反映させて確認してからようやくコントローラーやモデルの実装に入った。期しないで、上で挙げた落とし穴に嵌らなかったわけだ。</p>
<p><a href="http://guides.rubyonrails.org/">Rails Guides</a>(<a href="http://railsguides.jp/">日本語</a>)や<a href="https://www.railstutorial.org/">Rails Tutorial</a>(<a href="http://railstutorial.jp/">日本語</a>)、またはRails用の本を使ったわけではないので、正当な覚え方ではないのだろうと思っていた(その後Railsの本も何冊か読んではいる)し、何より人に勧めるような覚え方ではないと感じていたのだが、意外とそうでもないのかも知れない。しかし、この本は多少古い(XML、del.icio.us、Rails 2……)ので、やっぱり人には勧めにくい。そこで、『<a href="https://gihyo.jp/dp/ebook/2014/978-4-7741-7074-9">Webを支える技術</a>』の実践編という位置付けで、Railsを使ったハンズオンチュートリアルを書いてみるのはどうだろう。</p>
<div class="booklog_html"><table><tr><td class="booklog_html_image"><a href="http://www.amazon.co.jp/Web%E3%82%92%E6%94%AF%E3%81%88%E3%82%8B%E6%8A%80%E8%A1%93-HTTP%E3%80%81URI%E3%80%81HTML%E3%80%81%E3%81%9D%E3%81%97%E3%81%A6REST-WEB-PRESS-plus/dp/4774142042%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774142042" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51qo6pgjaSL._SL160_.jpg" width="107" height="150" style="border:0;border-radius:0;" /></a></td><td class="booklog_html_info" style="padding-left:20px;"><div class="booklog_html_title" style="margin-bottom:10px;font-size:14px;font-weight:bold;"><a href="http://www.amazon.co.jp/Web%E3%82%92%E6%94%AF%E3%81%88%E3%82%8B%E6%8A%80%E8%A1%93-HTTP%E3%80%81URI%E3%80%81HTML%E3%80%81%E3%81%9D%E3%81%97%E3%81%A6REST-WEB-PRESS-plus/dp/4774142042%3FSubscriptionId%3D0AVSM5SVKRWTFMG7ZR82%26tag%3Dbooklog.jp-22%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4774142042" target="_blank">Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)</a></div><div style="margin-bottom:10px;"><div class="booklog_html_author" style="margin-bottom:15px;font-size:12px;;line-height:1.2em">著者 : <a href="http://booklog.jp/author/%E5%B1%B1%E6%9C%AC%E9%99%BD%E5%B9%B3" target="_blank">山本陽平</a></div><div class="booklog_html_manufacturer" style="margin-bottom:5px;font-size:12px;;line-height:1.2em">技術評論社</div><div class="booklog_html_release" style="font-size:12px;;line-height:1.2em">発売日 : 2010-04-08</div></div><div class="booklog_html_link_amazon"><a href="http://booklog.jp/item/1/4774142042" style="font-size:12px;" target="_blank">ブクログでレビューを見る»</a></div></td></tr></table></div>
<p>そこまでいかなくても、いきなりRailsで手を動かすのではなくて、先にこの本を読んでおくことにするのはどうだろう。</p>
<p>なお、当たり前の断りとして、適した覚え方は人それぞれだ。特に、人は自分の習得方法がいい物だと思いがちなので、そういったバイアスもある。何より僕は今のRails 4、5は殆ど触っていないので、今でも通用する考え方かは分からない。</p>
https://diary.kitaitimakoto.net/2015/12/30.html
『RESTful Webサービス』でRailsを覚える
2015-12-30T00:00:00Z
2015-12-30T00:00:00Z
<p>何度か書いたように、この日記はPolymerで作っている、つまりウェブコンポーネントを使っている。そこで、一般的に使いそうな機能をMiddleman拡張として書いていたのだが、今日RubyGemとしてリリースした。<a href="http://www.rubydoc.info/gems/middleman-web_components">Middleman Web Components</a>だ。</p>
<p>もちろんまだまだ足りないことは多いんだろうと思うが、自分の日記を触りながら拡張していきたいと思う。</p>
https://diary.kitaitimakoto.net/2015/12/26.html
Middleman Web Components
2015-12-26T00:00:00Z
2015-12-26T00:00:00Z
<p>この日記は<a href="http://qiita.com/advent-calendar/2015/groonga">Groonga Advent Calendar 2015</a>の21日目の記事です。今日は25日です。大幅に遅れてしまって、本当に申し訳ありません。</p>
<p>Middlemanでブログの類似記事一覧を作る拡張に<a href="https://github.com/ngs/middleman-blog-similar">Middleman-Blog-Similar</a>がある。類似判定のアルゴリズムを選べたりといい所もあるのだがこれは今のMiddleman v4に対応していない。それにGroonga Advent CalendarのネタとしてもGroongaが使いたかったので、Groongaの類似文書検索機能を使って同様のことをやってみた。</p>
<p><a href="https://github.com/KitaitiMakoto/middleman-blog-similar-groonga">https://github.com/KitaitiMakoto/middleman-blog-similar-groonga</a></p>
<p>使うには<code>middleman-blog-similar-groonga</code>をインストールし、<code>config.rb</code>で</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">activate</span> <span class="ss">:blog_similar_groonga</span>
</code></pre></div></div>
<p>と追記する。</p>
<p>拡張を有効化するとブログ記事向けのレイアウトで<code>similar_articles</code>というヘルパーが使えるようになる。ここでイテレートされるオブジェクトには</p>
<ul>
<li><code>key.key</code>(パス)</li>
<li><code>title</code></li>
<li><code>path</code></li>
<li><code>body</code>(タグを取り除いたもの)</li>
</ul>
<p>の属性があって、わざわざ他記事の<code>BlogArticles</code>オブジェクトまで辿らなくても済むようになっている。類似記事のタイトルを表示しつつリンクにしたいだけなら充分だろう。</p>
<div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><section></span>
<span class="nt"><h2></span>類似記事<span class="nt"></h2></span>
<span class="nt"><ul></span>
<span class="cp"><%</span> <span class="n">similar_articles</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">article</span><span class="o">|</span> <span class="cp">%></span>
<span class="nt"><li></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="n">article</span><span class="p">.</span><span class="nf">title</span><span class="p">,</span> <span class="n">article</span><span class="p">.</span><span class="nf">key</span><span class="p">.</span><span class="nf">key</span> <span class="cp">%></span><span class="nt"></li></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
<span class="nt"></ul></span>
<span class="nt"></section></span>
</code></pre></div></div>
<p>として使うことができる。</p>
<p>今は本文での類似しか見ていないが、タイトルやタグを使ったほうが人間の感覚に合うだろうから、改善を続けていきたい。</p>
<p>名前は似ているがMiddleman-Blog-Similarとは全く互換性が無いので注意すること。</p>
https://diary.kitaitimakoto.net/2015/12/25.html
Middleman Blogの類似記事をGroongaを使ってリストアップするRubyGemを作った
2015-12-25T00:00:00Z
2015-12-25T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/35021">Groongaで学ぶ全文検索 2015-12-18</a>に行って来た。今日のお題はドリルダウン(ファセット検索)。</p>
<h2 id="section">ドリルダウン</h2>
<p>ある検索語で検索した時に、検索結果をさらに絞り込む<ins>と何件になるかなどの集計をする(発表した時に間違いを指摘してもらった。ドリルダウンは絞り込みではなく集計)</ins>ことを、予め(検索時に同時に)Groongaがやっておいてくれる、という機能だ。例えば<a href="http://docs.ruby-lang.org/ja/search/">るりまサーチ</a>で、「call」を検索した場合、callを含むページのほか、画面左にインスタンスメソッドが何件あるか、特異メソッドは何件あるか……といった結果も表示されている。これは、「callを含み、かつインスタンスメソッドのページ」は何件あるか、という数になっている。</p>
<p>このように、検索結果に対して、既に(与えられた)キーで集計した結果を返すのがドリルダウン。まず、既に分類が終わっているので、検索結果を絞り込む作業が簡単に(クリックするだけで)できる。また、0件の時はそのことが分かるので、不要な絞りこみ作業をユーザーがわざわざする必要がない。というように、ドリルダウンは全文検索を補助する機能だ。全文検索とは関係ない文脈では集計機能ということになる。</p>
<p>ドリルダウンを高速にするために、Groongaのデータ構造が活きている。RDBMSは行指向のテーブル型データベースだから、ストレージ上、一行のデータが複数カラム分、まとまった場所に置かれるようになっている。あるカラムに関する集計(インスタンスメソッドは何件で、特異メソッドは何件で……)をする場合には各行をループさせて、その中で注目しているカラムの場所まで移動して、内容に応じて集計結果を更新する必要がある。</p>
<p>Groongaは列指向データベースだから、ストレージ上、あるカラムのデータがまとまった場所に置かれている。だから集計する場合には一箇所からデータをまとめて取って来て数えたりしていけばよい。</p>
<p>集計に関してはこうなっていて、全文検索では更に「検索結果の中で」のそういった集計を行うことになるが、特別なことはない。一旦全文検索を行ってレコードを取得する。その中で更に集計する。ヒットしなかったレコード分はスキップして集計するのでややもったいないが、列指向でのやり方よりは効率がいい。</p>
<h2 id="section-1">多段ドリルダウン</h2>
<p>多段ドリルダウンはどうやっているのかも聞いた。本のデータベースがあった時に、雑誌 -> プログラミングというように下位分類を持つようなやつだ(こうじゃないタイプの「多段ドリルダウン」も考えられるそうだが思い出せないとのこと)。</p>
<p>Groongaにはキーを二個使ってドリルダウンができる機能があるそうだ。列指向データベースなので大分類カラム(「雑誌」)、小分類カラム(「プログラミング」)のデータはそれぞれの場所にまとまって置かれているが、ドリルダウンする時に、それぞれから同じレコードの物を取り出してペアにしてまとめることができるのだ。</p>
<p>例えば、何かで検索して結果セットが得られた後に、ドリルダウン結果を大分類と小分類のペアである[雑誌,プログラミング]、[ハードカバー,小説]……の集まりというデータとみなして作ることができる。つまり「大分類が雑誌で小分類がプログラミング」というレコード(本)が何件あるか、という結果を作ることができる。</p>
<p>この結果を使えば、大分類+小分類での絞り込みをサポートすることができる。が、これだけだと大分類のみでの絞り込み結果を出さない。その結果も勿論欲しいので、更に大分類カラムのみの集計もする……ということはしないで、Groongaは頑張って、あるカラムの一回のスキャンで色んな結果用の処理を行うようになっているらしい(ということは、インデックスを調べる前の計画を頑張っているということかな?<ins>-> 別に頑張ると言うほど大変なことではなかった</ins>)。</p>
<p>また、この話は(Groongaでは)二個に限らず、n個に一般化できるらしい。</p>
<h2 id="section-2">ユーザー定義の集約関数</h2>
<p>参加者から「平均、最大値……」といった予め用意された関数以外の、ユーザーが定義した関数は使えるのか、という質問がでた。結論は「できない」。</p>
<p>一つはいいインターフェイスがないから。もう一つは、そういう機能を入れると遅くなってしまうから。</p>
<h2 id="section-3">ドリルダウン結果のデータ構造</h2>
<p>ドリルダウンの結果はハッシュテーブルになっている。</p>
<p>分類カラムの集計処理中、「雑誌」という物を見付けた場合、 「雑誌」が、ドリルダウン結果に既にあるかどうかを素早く知る必要があるからだ。なければ結果データに「雑誌」を加えて「1件」というデータにすることになるし、あれば既存の値をインクリメントする必要がある。</p>
<p>ハッシュのキーはカラムの値その物だとして、バリューの方は、Groongaでは「バリュー」という物になっているらしい。配列とかではない。どんなデータでもよい長さの決まったバイト列で、それを使用する機能の方で適宜解釈して使う、とのこと。</p>
<p>このバリューの方は、ここまでの話だと合計や最大値などになるので、単独の数値でよさそう。だがもっと別の物を入れてもよくて、実際、全文検索結果レコードの内容を入れると便利になる。ドリルダウン結果に加えて、より詳細な結果を同時に見せられるからだ。例えばGoogle検索でたまに、サイトの下位ページが出ることがあるが、そういった情報を出すために検索結果データの内容を使うことができる。</p>
https://diary.kitaitimakoto.net/2015/12/18.html
ドリルダウン(ファセット検索)の仕組み
2015-12-18T00:00:00Z
2015-12-18T00:00:00Z
<p>今日の日記は<a href="http://qiita.com/advent-calendar/2015/groonga">Groonga Advent Calendar 2015</a>の五日目です。昨日は<a href="http://qiita.com/cosmo0920">cosmo0920</a>さんの<a href="http://qiita.com/cosmo0920/items/ed7e071d111c533e217c">Groonga族のHomebrewの変遷を振り返る</a>でした。やっぱりコマンド一つで簡単にインストールできるのはよい。しかし、そのためには陰で誰かが苦労しているということも伺える記事だった。</p>
<p>今日は、Homebrewなどで一発インストールのできないGroonga族の一員、<a href="http://droonga.org/ja/">Droonga</a>のインストールについて書く。</p>
<p>Groongaにはレプリケーション機能がない。DroongaはGroongaを複数のマシンにレプリケーションさせるプロダクトだ。公式サイトのほか、<a href="http://qiita.com/advent-calendar/2014/groonga">去年のGroonga Advent Calendar</a>にも記事があって、とても面白く読んだ。Droongaが何かということはこれらを見てほしい。</p>
<p>公式サイトには勿論<a href="http://droonga.org/ja/install/">インストール手順</a>も書かれているのだが、今日時点でこれはうまくいかない。そこで僕は、サーバーの構成管理ツールである<a href="http://itamae.kitchen/">Itamae</a>のレシピを作ってインストールしている。今日はそれを使ったインストール方法を書こうと思う。以下、<a href="https://www.vagrantup.com/">Vagrant</a>を使ってUbuntu 15.04の環境で実行している。マシンイメージは<a href="https://atlas.hashicorp.com/boxes/search?utm_source=vagrantcloud.com&vagrantcloud=1">Vagrant Cloud</a>から<a href="https://atlas.hashicorp.com/ubuntu/boxes/vivid64">公式のイメージ</a>を持って来た。</p>
<p>Droongaのインストール時にはメモリーが必要なので、<code>Vagrantfile</code>に設定を書いて2GiBくらい確保しておく。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Vagrant</span><span class="p">.</span><span class="nf">configure</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">define</span> <span class="ss">:ubuntu1504</span> <span class="k">do</span> <span class="o">|</span><span class="n">node</span><span class="o">|</span>
<span class="n">node</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">box</span> <span class="o">=</span> <span class="s2">"ubuntu/vivid64"</span>
<span class="n">node</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">provider</span> <span class="s2">"virtualbox"</span> <span class="k">do</span> <span class="o">|</span><span class="n">provider</span><span class="o">|</span>
<span class="n">provider</span><span class="p">.</span><span class="nf">memory</span> <span class="o">=</span> <span class="mi">2048</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># :</span>
<span class="c1"># :</span>
<span class="k">end</span>
</code></pre></div></div>
<p>バーチャルマシンを起動したら、まずログインして環境を更新する。</p>
<pre><code>[host]$ vagrant up ubuntu1504
[host]$ vagrant ssh ubuntu1504
[vm]$ sudo apt-get update
[vm]$ sudo apt-get upgrade -y
</code></pre>
<p>ここでようやくレシピの登場。GitHubに上げている(<a href="https://github.com/KitaitiMakoto/itamae-plugin-recipe-droonga">https://github.com/KitaitiMakoto/itamae-plugin-recipe-droonga</a>)。gemまたはItamaeプラグインの形をしているがrubygems.orgには上げていないので、<code>git clone</code>で持って来る必要がある。もっと一般的にしてからリリースしたいなと思って、そのまま時間が過ぎてしまっているのだ……。</p>
<pre><code>[host]$ git clone https://github.com/KitaitiMakoto/itamae-plugin-recipe-droonga.git
</code></pre>
<p>リポジトリーをクローンしたら、Itamaeのレシピファイル(ここでは<code>recipe.rb</code>)を用意して、以下のように一行書く。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">include_recipe</span> <span class="s2">"./itamae-plugin-recipe-droonga
/lib/itamae/plugin/recipe/droonga/default.rb"</span>
</code></pre></div></div>
<p>そうしたらItamaeを実行すればよい。簡単だ。但し時間は掛かる。</p>
<pre><code>[host]$ itamae ssh --vagrant --host ubuntu1504 recipe.rb
</code></pre>
<p>(上のイメージだとこれでいいが、DigitalOceanだと依存パッケージが足りなくてうまくいかなかったかも知れない。エラーメッセージを見ながら必要な物をインストールしてほしい。)</p>
<p>Droongaは二つのコンポーネントからなっている。Groongaデータベースを操作したり、他ノードと連携してレプリケーションを実現する<a href="https://github.com/droonga/droonga-engine">Droonga Engine</a>と、そこへのHTTPインターフェイスを提供する<a href="https://github.com/droonga/droonga-http-server">Droonga HTTP Server</a>だ。それぞれそれ用のプロセスを起動する必要がある。</p>
<p>公式サイトの記事では<code>service</code>コマンドを使ってこれをコントロールすることになっているが、Ubuntuでは15.04からUpstartに代わってsystemdが導入されたので、レシピでは<code>systemctl</code>コマンドを使うようにしている。</p>
<pre><code>[vm]$ sudo systemctl status droonga-engine
● droonga-engine.service - Droonga Engine
Loaded: loaded (/lib/systemd/system/droonga-engine.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2015-12-04 20:09:56 UTC; 37s ago
Main PID: 30190 (droonga-engine)
CGroup: /system.slice/droonga-engine.service
(snip)
[vm]$ sudo systemctl status droonga-http-server
● droonga-http-server.service - Droonga HTTP Server
Loaded: loaded (/lib/systemd/system/droonga-http-server.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2015-12-04 20:09:57 UTC; 2min 11s ago
Main PID: 30228 (node)
CGroup: /system.slice/droonga-http-server.service
(snip)
</code></pre>
<p>これでDroongaが動くはずだし、実際<a href="http://epub-searcher-demo.kitaitimakoto.net/">EPUB Searcherデモサイト</a>ではこの方法でインストールして、現在でも動作している。</p>
<p>尚、Droongaを動かすには、内部・外部から、<code>hostname</code>で返って来るホスト名で名前解決できる必要がある。<code>hostname</code>と違うホスト名を使いたい場合は、レシピのインストールの箇所(<a href="https://github.com/KitaitiMakoto/itamae-plugin-recipe-droonga/blob/87c7c9015b626a84b14bfa226d399eb02839bd84/lib/itamae/plugin/recipe/droonga/default.rb#L28">Droonga Engine該当箇所</a>、<a href="https://github.com/KitaitiMakoto/itamae-plugin-recipe-droonga/blob/87c7c9015b626a84b14bfa226d399eb02839bd84/lib/itamae/plugin/recipe/droonga/default.rb#L36">Droonga HTTP Server該当箇所</a>)の最後の<code>bash</code>実行時に<code>HOST</code>環境を設定し、また<code>/etc/hosts</code>でも設定する必要があるので書き換えること。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># :</span>
<span class="c1"># :</span>
<span class="n">execute</span> <span class="s2">"curl -sL https://deb.nodesource.com/setup_0.12 | bash HOST=..."</span> <span class="k">do</span>
<span class="n">not_if</span> <span class="s2">"test -e /etc/apt/sources.list.d/nodesource.list"</span>
<span class="k">end</span>
<span class="c1"># :</span>
<span class="c1"># :</span>
<span class="n">execute</span> <span class="s2">"curl https://raw.githubusercontent.com/droonga/droonga-engine/master/install.sh | bash HOST=..."</span> <span class="k">do</span>
<span class="n">not_if</span> <span class="s2">"type droonga-engine"</span>
<span class="k">end</span>
<span class="c1"># :</span>
<span class="c1"># :</span>
</code></pre></div></div>
https://diary.kitaitimakoto.net/2015/12/05.html
DroongaをインストールするItamaeレシピ
2015-12-05T00:00:00Z
2015-12-05T00:00:00Z
<p>この日記は<a href="https://www.polymer-project.org/">Polymer</a> 1.2.1で作っているのだが、この前まで僕のメインブラウザーであるFirefox for Androidでは読めなかった。今でも<a href="https://elements.polymer-project.org/">Polymer Element Catalog</a>のサイトを見るとそれが体験できる。Firefox for PCでは問題ない。Firefox for iOSは知らない。</p>
<p><a href="http://webcomponents.org/polyfills/">webcomponentsjs</a>やPolymerに<code>console.log()</code>を仕込みながらプリントデバッグを頑張って原因を突き止めたところ、webcomponentsjsでの<a href="http://www.w3.org/TR/html-imports/">HTMLインポート</a>の検出に問題があることが分かった。現時点でのwebcomponentsjsでは、ブラウザーにHTMLインポートの機能があるかどうかを、<code>link</code>要素の(JavaScriptの)オブジェクトに<code>import</code>プロパティ(あれば関数)が存在するかどうか、<code>in</code>演算子で確認してチェックして判断している(<a href="https://github.com/webcomponents/webcomponentsjs/blob/fedfe0210aa853a9531bd976f6d161d585cc22fb/src/HTMLImports/base.js#L28">該当箇所</a>)。HTMLインポートをサポートしていないブラウザー(Firefox for PCなど)では<code>import</code>プロパティが存在せず、その場合はshimを使う。ところがFirefox for Androidでは、「<code>link</code>要素に<code>import</code>プロパティが存在する」「しかしHTMLインポート機能はサポートしていない」ということになっている。<code>link.import</code>が、<code>null</code>になっているのだ。たとえ<code>null</code>であっても、値が存在すれば<code>in</code>演算子は<code>true</code>を返す。従ってFirefox for AndroidにはHTMLインポート機能が存在する、とwebcomponentsjsは判断しているわけだ。</p>
<p>一応、<a href="https://github.com/webcomponents/webcomponentsjs/issues/452">バグレポート</a>はした。プルリクエストはリクエストしなかった。<a href="https://github.com/webcomponents/webcomponentsjs/blob/master/CONTRIBUTING.md">コントリビューションページ</a>によると、コントリビュートするにはライセンスに同意する必要がある。それは構わなかったのだが、同意手続きの過程で住所を入力欄が現れた。それも必須項目として。漠然と不安を覚えてプルリクエストは躊躇ってしまった。</p>
<p>webcomponentsjsでこの問題が対応されるかは分からない。だから今この日記ではこんなワークアラウンドを入れている。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">ua</span> <span class="o">=</span> <span class="nb">navigator</span><span class="p">.</span><span class="nx">userAgent</span><span class="p">;</span>
<span class="nf">if </span><span class="p">(</span><span class="nx">ua</span><span class="p">.</span><span class="nf">indexOf</span><span class="p">(</span><span class="dl">"</span><span class="s2">Android</span><span class="dl">"</span><span class="p">)</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span> <span class="o">&&</span>
<span class="nx">ua</span><span class="p">.</span><span class="nf">indexOf</span><span class="p">(</span><span class="dl">"</span><span class="s2">Firefox</span><span class="dl">"</span><span class="p">)</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span> <span class="o">&&</span>
<span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">link</span><span class="dl">"</span><span class="p">).</span><span class="k">import</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">delete</span> <span class="nx">HTMLLinkElement</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="k">import</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">})();</span>
</code></pre></div></div>
<p>これを、webcomponentsjsをロードする<code>script</code>タグの<strong>前</strong>に置いている。<code>if</code>節の条件は<code>null</code>のチェックだけでよさそうだが、そうするとなぜかChromiumやChromeでページが読めなくなってしまったので、プラットフォームも判断している。なぜ読めなくなったかは調べていない。</p>
https://diary.kitaitimakoto.net/2015/12/03.html
Firefox for AndroidでもPolymerが動作するようにする
2015-12-03T00:00:00Z
2015-12-03T00:00:00Z
<p>とても久し振りに<a href="https://sendagayarb.doorkeeper.jp/">Sendagaya.rb</a>に参加して来た。</p>
<p><a href="https://sendagayarb.doorkeeper.jp/events/35276">第128回</a>の今日は、三十分くらい雑談した後、QiitaのAction Cableの記事<a href="http://qiita.com/bisque33/items/1360477c2260b361ec03">[Rails5]Action Cableのサンプルを読み解いてみる</a>を読みながらああだこうだ言っていた。一通り見ての感想は「便利そう」「使いたい」。</p>
<p><a href="https://github.com/rails/actioncable">Action Cable</a>はRails 5から入るらしい新機能で、WebSocketをRailsに統合した形で扱える物らしい。まともに記事など読んだのは今日が初めてで、これがRais界隈でどれくらい認知されているかは分からない。僕が「RailsでWebSocket」と聞いて漠然と思い浮かべたのが<code>ActionController::Live</code>だったのだけど全然違う(<code>ActionController::Live</code>については<a href="http://tenderlovemaking.com/2012/07/30/is-it-live.html">Is It Live?</a>がよい紹介記事だ)。</p>
<p>Action Cableでは、Railsのプロセスの他にAction Cable用のプロセスを立ち上げる。こいつがブラウザーとWebSocketで通信する。普通だ。Action Cableのいい所はここからで、Railsとセッション用のクッキー情報を共有できる(電子署名が付いているあれだ)。だから、WebSocketを使ってAction Cableに接続してきたクライアントが、Rails(のデータベース)で管理しているどのユーザーに相当するのか、見付けることができるのだ。</p>
<p>更に、Railsのプロセスからブラウザーに、WebSocket経由でメッセージを送ることができる。例えば、フォームなどから普通にコメントを投稿した時に、そのことをWebSocketで繋がっている全ユーザーに通知できる。だがRailsがWebSocketを使ってAction Cableに接続しているわけではない。Active Jobを使ってRedisにメッセージを送信するのだ。Action CableはRedisのpubsub機能を使っていて、Rails(Active Job実装のワーカープロセス)がパブリッシャー、Action Cableプロセスがサブスクライバーになっている。Action Cableはサブスクライブしたメッセージを、予めAction Cable用に書かれたコードに従って、必要なクライアントに流す。もちろん、クライアント同士WebSocket経由での通信もできる。(そう言えば、はて、クライアントからDBのレコードを弄るような場合、Action Cableプロセスがやるのだろうか、Railsプロセスがやるのだろうか。後者はフォームなりAjaxなりでやることが自然に思い浮かべられるが、前者は逆方向のpubsubになる? と考えると、そういうことはなさそうだなと思う。)</p>
<pre><code>Rails --(Active Job)--> Worker --(Redis pubsub)--> Cable --(WebSocket)--> Clients
</code></pre>
<p>副産物として、始めからRedisのpubsubでWebSocketサーバーをつなぐのが前提なので、プロセスを増やすだけで簡単にスケールアウトさせられそうだ。これは悪いことではない、というかむしろいいことだが、Railsはモノリシックなのが特徴の一つという印象を持っていたので、結構変わり種のコンポーネントだな、と感じた。繰り返すが悪いことではない。</p>
<p>と、便利なところだったが、実は半分くらいは推測で書いている。件の記事の内容からは内部の動きは分からないからだ。だから今度はAction Cableのソースコードを読みたいと思っているし、もしかしたら次回のSendagaya.rbでソースコードリーディングができるかも知れない。</p>
<p>余談。「RailsからRedisにパブリッシュするためにはSidekiqなどが必要で、更に別のプロセスを立てないといけない」といった話をしている時に、<a href="https://twitter.com/tkawa">@tkawa</a>さんに<a href="https://github.com/brandonhilkert/sucker_punch">Sucker Punch</a>を教えてもらった。Railsプロセス内にCelluloidを使ってアクタースレッドを立て、それを使ってActive Jobのジョブを実行する物のようだ。ぱっと見本番で使っていいかは不安に思ったが、開発環境で使う分には便利だろう。</p>
https://diary.kitaitimakoto.net/2015/11/30.html
Action Cableが便利そう
2015-11-30T00:00:00Z
2015-11-30T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/31482">Groonga Meatup 2015</a>で発表してきた。タイトルは「Rubyでプラグインを作れる分散全文検索エンジンDroonga」。発表を録画してもらえていて、以下で見られる。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/9TfYtKolbcg?list=PLLwHraQ4jf7MRIR36oO_Zys4V5oJ8DGDb" frameborder="0" allowfullscreen=""></iframe>
<p>(<del>あとで個別に切り分けたものが出てきそう</del><ins>出てきたので差し替えた。ありがとうございます!</ins>)</p>
<p>資料は以下の通り。
(iframeで埋め込んでいるけど、HTTPS非対応なので埋め込み表示できていないかも知れない。リンク先に飛ぶか、このページのURIをHTTPにすると見られる。)</p>
<p><a href="http://www.storyboards.jp/viewer/yct228">http://www.storyboards.jp/viewer/yct228</a></p>
<iframe src="http://www.storyboards.jp/widget/yct228" width="640" height="480"></iframe>
<p>今年のテーマ(去年までは「全文検索エンジンGroongaを囲む夕べ」という名前でやっていたイベント)は「よいところ」なので、Droongaのよいところを発表してきた。発表の機会を貰えたことはとても価値のあることだった。本当にありがとうございました。</p>
<p>反省点色々あった。ただ、これに関する色々なことを実際のDroonga開発者の方に聞けて、三つくらい疑問が解消したのがあって、僕にとってはとても有意義だった。ありがとうございました。</p>
<p>あと、プレゼンに使った<a href="http://www.storyboards.jp/">Stobo</a>はそれなりに面白がってくれて、自分で作った物ではないけどこれも嬉しかった。本当は<a href="http://rabbit-shocker.org/ja/">Rabbit</a>でスライド作って<a href="http://slide.rabbit-shocker.org/">Rabbit Slide Show</a>に上げたいなと思っていたのだけど時間無さ過ぎて慣れてるツールになったというのは秘密。</p>
https://diary.kitaitimakoto.net/2015/11/29.html
Rubyでプラグインを作れる分散全文検索エンジンDroonga
2015-11-29T00:00:00Z
2015-11-29T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/34337">Groongaで学ぶ全文検索 2015-11-20</a>に参加して来た。遅れそうで「遅れます」って連絡してたら、15分くらい早く着いてしまって時間のお見積りが不正確で大変申し訳ございませんでした。</p>
<p>今日のテーマは日本語での全文検索。</p>
<p>以前、英語での全文検索の仕組みについてはやった(<a href="http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150918">http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150918</a>)。今回は軽くそれを復習した後、日本語では英語の場合と違ってどういうところを頑張る必要があるかという話だった。最初に、知ってるだろと説明役を振られそうになったけど「日本語の方は分からないんですよ」と言って断った。が、日本語で分かってない部分(分かち書きの仕方)まで辿り着かなかったので、引き受けておけばよかったなあ。</p>
<p>さておき、まず、英語での全文検索のおさらい。以下、話を単純化するため、一語のみでの検索という前提にする。</p>
<p>全文検索は検索語を入力として、それが含まれた文書を返すもの。単純に、検索語に対して、登録されている文書の一つ一つを調べていくと、文書が増えるにつれどんどん検索が遅くなってしまう。これを防ぐために、インデックスを作り、それに対して検索するようにする。</p>
<p>例えば、「Groonga」で検索した時、登録されている文書から「Groonga」という言葉が含まれている文書(のIDなど)の一覧を返す。この時インデックスにはどういったデータが入っていると嬉しいか? どういう構造になっていると嬉しいか? キーが分かると値がすぐに分かる類のデータ構造がよい。配列やハッシュテーブルなどである。</p>
<p>このデータ構造を使って、キーには検索でキーワードとしてヒットさせたい物(「Groonga」「Mroonga」……)が入っているようにする(逆に言うと<strong>、ここに入っている物だけが、キーワードとして検索可能</strong>になる)。値には、そのキーワードを含む文書(のIDなど)の一覧を入れておく。すると、「Groonga」で検索した時に、このハッシュテーブルなりを使えば、すぐに「『Groonga』を含む文書一覧」が手に入る。</p>
<p>英語だろうが日本語だろうが、ここまでの考え方は同じ。英語ではここまでで大枠の話は尽きる。データ構造のキーに入れる物が、英単語と一致すること殆どだからだ。多くの場合、検索は、単語で行う。「Groonga」を含む文書が欲しい時に「Groo」みたいな中途半端な文字列で検索したりはしない。こうして検索語の種類(英単語)とインデックスのキーの種類(英単語)が一致するので、英語の場合は概ねこれで要求を満たせる。</p>
<p>ところが日本語ではそうはいかない。</p>
<p>例えば東京都について書かれた文書を探したい時に、検索キーワードとして「東京都」を使うこともあれば「東京」を使うこともある。インデックスのキーに「東京都」だけ入れておけばよいということにはならない。そうしてしまうと、「東京」で検索した時に、その語に<strong>一致</strong>するキーが無いわけなので、「東京を含む文書がない」という結果になってしまう。これは、日本語では、単語の区切りが明確ではないという性質に由来する。「単純に単語を入れておけばいいというわけではない。なぜなら単語で検索しない(複合語などで検索する)かも知れないからだ」といこと。(最初に「一単語で検索する」という前提を置いたけど、そもそも日本語では「一単語がどこまでか」が自明ではない、ということだと思う。ちょっとここ自信無い。)</p>
<p>さて、この問題の解決には大きく分けて二種類のアプローチがある。一つは、英語同様単語をキーワードにすること(アプローチA)。「花が咲いた」という短い文書があった時、「花」「が」「咲いた」をキーとしてインデックスに入れる(「咲いた」は微妙かも知れないけどここではそうする)。こうしておくと、「花」で検索した時、「が」で検索した時……に、正しくこの文書を見付けられる。</p>
<p>もう一つは、単語を気にせず何でもキーにしてしまうこと(アプローチB)。「花が」みたいな複合語も「咲い」みたいな単語になってない文字列(ということにしてください)も何でも、意味を気にせずキーにする。</p>
<p>この二種類のアプローチがあって、両方よく使われている。なぜ一つでなく二つあるかというと、アプローチAにある種の難しさがあるからだ。どういう難しさかというと、</p>
<ul>
<li>「すもももももももものうち」みたいに、単語の切り方が難しい(「スモモも桃も桃のうち」)</li>
<li>「ここではきもの」みたいに、切り方に複数の候補があって選ぶのが難しい(「ここでは、着物」「ここで、履物」)</li>
</ul>
<p>など(他にもある?)。</p>
<p>アプローチAは、検索時にやることが少なくなりやすいという特徴がある。多くの場合、検索語は単語になる。今、インデックスのキーとしては単語を入れているので、単純にハッシュテーブルなどを引けばよくなり、速い。</p>
<p>アプローチBは、例えば上で説明したように、文書を二文字ずつ区切ってキーにしている場合。この場合は、検索語が「咲いた」だと三文字なので、そんなキーは存在せず、(本来ヒットするべき)文書がヒットしない。これを防ぐために検索語の方も、インデックスのキーの長さ(二文字)に合わせてばらばらにする必要がある。(<del>まず、この処理の分、検索時にはすることが増え、遅くなる。でも多分、これはあまり気にしなくていい遅さで、次の話のほうが支配的だろう。</del><ins>ということをまとめ発表で言ったら、<a href="https://twitter.com/ktou">@ktou</a>さんが訂正してくれた。アプローチAでも分割しているらしい。検索クエリーが単語になってくれていれば、そういう制約を設けることができれば分割しなくていいが、そうでない場合がほとんどなので。……振り返ると、アプローチA=形態素解析を使った全文検索で、クエリーも解析しているのは知っていたはずだった……。</ins>)</p>
<p>「咲いた」を二文字でバラバラにすると「咲い」と「いた」。このそれぞれのキーについて文書を検索する。すると、「『咲い』を含む文書一覧」と「『いた』を含む文書一覧」が手に入る。これらの文書には「『咲い』は含まれるが『いた』は含まれない、従って『咲いた』は含まれない」という文書と、この「咲い」と「いた」の関係をひっくり返した文書が含まれていて、これらはユーザー(プログラム)に渡す検索結果からは除きたい。しかも、除くだけでは不十分で、「花が咲いていた」という文書も、現時点での「正解」の文書リストには含まれてしまっている。でもここに「咲いた」の語はない。「咲い」と「いた」がこの順番で隣り合っていないといけないわけだ。今手に入っている文書のうち、この点も満たす文書を更に絞り込む必要がある。</p>
<p>この絞り込みの方法は二つある。一つは、インデックスの、それぞれのキーに対応する情報に、(文書IDなどの識別子のほか)文書中の出現位置(何文字目に出現するキーか)という情報も入れておく方法。こうしておけば、検索時に「『咲い』と『いた』を含み、その出現位置が一文字違い」という文書を探せばよいことになる。(説明されなかったが、「『咲い』を含み、その出現位置の次の位置が『いた』である」という検索方法だと、集合としては同じ結果が得られるけど、だいぶ遅くなってしまうはず。「咲い」を含む文書一覧を取得した後、それぞれの中身を先頭から一文字ずつ調べていく必要があるので、文書自体を読み込んだり、文字検索用のカーソルを動かしたりする必要が出てきてしまう。)</p>
<p>もう一つは、無駄を承知で、まず、<del>「咲い」を含む文書一覧と「いた」を含む文書一覧を両方取得してしまう。</del><ins>「『咲い』と『いた』を両方含む文書一覧」を取得して、(これも訂正してもらった。)</ins>その後に文書それぞれを調べて、キーが隣り合っているか(「咲いた」と連続しているか)を調べる。</p>
<p>アプローチBはこうして、アプローチAよりも処理が増えているので、難しさは減るが、検索時に遅くなってしまう。</p>
<p>というのが理屈。ここまで説明したところで、専門用語が導入された。</p>
<ul>
<li>アプローチA … 形態素解析</li>
<li>アプローチB … N-gram(Nのところは文字数。N=2でバイグラム、3でトリグラム)</li>
</ul>
<p>こうしてキーワードが連続しているかをチェックして、連続している物だけを返す検索方法をフレーズ検索と言う。一応形態素解析を使った検索で使われることもあるが、多くはN-gram検索で使われる。形態素解析を使う場合は、そもそもの形態素解析を使う目的から(インデックスのキーが単語になっているので)、単語で検索して問題ないことが殆ど。そして多くの人は単語で検索する。逆にN-gramの場合は、「いた」を含む文書とか基本的にノイズばっかりになるから、きちんと(二文字を越える)欲しい単語を含んでいるのかチェックしないと使い物にならない。</p>
<p>以上、英語での検索の場合の他に、日本語で頑張らないといけない処理。</p>
<p>余談。参加者の中に、Mroongaを使っていてMeCabの「too long sentence」といった内容のエラーに遭遇したという人がいた。これについても<a href="https://twitter.com/ktou">@ktou</a>さんが解説してくれた。これはMeCabの制限に引っかかったために発生したエラーとのこと。MeCabでは入力された文書に対して、句読点などを見て文に分割しようとする。ところが、文を分割する目印を見付けられなくて一文が長くなりすぎるような文書があると、リソース不足でこのようなエラーになってしまう。</p>
<p>最近のGroongaではこれの対策も実装されているらしい。オプションを指定することで、「一文が長くなり過ぎたら強制的に途中で切ってMeCabに渡す」ということができるようになる。これまで全体がエラーになっていた所が、この部分だけちょっとおかしな検索結果になるというだけなので、全体としてはまあまあうまくいっているらしい。</p>
https://diary.kitaitimakoto.net/2015/11/20.html
日本語文書の全文検索
2015-11-20T00:00:00Z
2015-11-20T00:00:00Z
<p><a href="https://middlemanapp.com/jp/">Middleman</a>でシンタックスハイライトするには<a href="https://github.com/middleman/middleman-syntax">middleman-syntax</a>がある。でもこの日記はMiddleman v4で作っていて、middleman-syntaxは今日時点ではv4に対応していない。のでフォークして対応させようかなと思ったけど、ふと思い出した。<a href="http://kramdown.gettalong.org/">kramdown</a>がそもそもシンタックスハイライトに対応しているはずだ。</p>
<p>kramdownはRuby製のMarkdownパーサー+αのライブラリーで、MiddlemanでMarkdownを使う時のデフォルトエンジンにもなっている。MiddlemanでのMarkdownエンジンの設定方法、調整方法は公式ドキュメントにある(<a href="https://middlemanapp.com/jp/basics/template_engine_options/">テンプレートエンジンオプション</a>)。<code>markdown_engine</code>設定はデフォルトのままで構わないので、<code>markdown</code>設定だけ、config.rbにこう追加した。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span> <span class="ss">:markdown</span><span class="p">,</span> <span class="s1">'syntax_highlighter'</span> <span class="o">=></span> <span class="s1">'rouge'</span>
</code></pre></div></div>
<p>これで、Markdownの記事でのコード部分にシンタックスハイライト用の<code><span></code>が追加される。ここでは<code>rouge</code>を指定しているけど<code>coderay</code>も使える(gemは自分でGemfileに書いてインストールする必要がある)。この設定ではブロックレベルのコードだけでなく、文中の<code>span</code>要素もハイライトされる。この日記ではそのままにしているけど、嫌な場合には</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span> <span class="ss">:markdown</span><span class="p">,</span> <span class="s1">'syntax_highlighter'</span> <span class="o">=></span> <span class="s1">'rouge'</span><span class="p">,</span>
<span class="s1">'syntax_highlighter_opts'</span> <span class="o">=></span> <span class="p">{</span>
<span class="s1">'span'</span> <span class="o">=></span> <span class="p">{</span><span class="s1">'disable'</span> <span class="o">=></span> <span class="kp">true</span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>と、<code>span</code>の時だけ無効化してやればいい。その他、使えるオプションはkramdownサイトの<a href="http://kramdown.gettalong.org/syntax_highlighter/rouge.html">Syntax Highlighting With Rouge</a>というドキュメントにあるので参照されたい(Coderayの場合は<a href="http://kramdown.gettalong.org/syntax_highlighter/coderay.html">Syntax Highlighting With Coderay</a>)。</p>
<p>これだけでは単に<code><span></code>が追加されるだけでスタリングはされない。CSSを追加する必要がある。Rougeで使えるテーマは<a href="https://github.com/jneen/rouge/tree/master/lib/rouge/themes">Rougeのthemesディレクトリー</a>を見ればよくて、この日記ではGitHubテーマを使っている。stylesheets/highlight.css.erbというファイルを作って、こう書いた:</p>
<div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><%=</span> <span class="no">Rouge</span><span class="o">::</span><span class="no">Themes</span><span class="o">::</span><span class="no">Github</span><span class="p">.</span><span class="nf">render</span><span class="p">(</span><span class="ss">scope: </span><span class="s1">'.highlighter-rouge'</span><span class="p">)</span> <span class="cp">%></span>
</code></pre></div></div>
<p>この設定には<a href="http://3100.github.io/">Room 3100</a>というブログの<a href="http://3100.github.io/blog/2013/10/31/monokai-theme-to-middleman-blog.html">middleman-blogのハイライトにMonokaiテーマを使う</a>という記事を大いに参考にさせてもらった。ありがとうございます。</p>
<p>あとは「`…`」(インライン)やフェンストコードブロックでマークアップしてやればよい。注意が必要なのは、kramdownのコードブロックはGitHubのフェンストコードブロックとちょっと違って、「<code>~</code>」(チルダ)を三つ並べる(<a href="http://kramdown.gettalong.org/syntax.html#language-of-code-blocks">Language of Code Blocks</a>)。</p>
<pre><code>~~~ ruby
set :markdown, 'syntax_highlighter' => 'rouge'
~~~
</code></pre>
https://diary.kitaitimakoto.net/2015/11/07.html
Middleman v4でシンタックスハイライト
2015-11-07T00:00:00Z
2015-11-07T00:00:00Z
<p><a href="https://groonga.doorkeeper.jp/events/33701">Groongaで学ぶ全文検索 2015-11-06</a>に参加してきた。</p>
<p>今日のお題は「精度」。</p>
<h2 id="section">精度とは</h2>
<p>精度とは何か、参加者からはこんな意見が出た。</p>
<ul>
<li>ユーザーの満足度</li>
<li>思った物が探せてる</li>
<li>もれなく検索できてる</li>
<li>ごみがない</li>
</ul>
<p>ここから<a href="https://twitter.com/ktou">@ktou</a>さんの説明。精度に入る前に、再現率と適合率の説明があった。</p>
<h3 id="section-1">再現率</h3>
<p>検索した時に返すべき物があるはず。<br />
例えば「京都」で検索した時、「京都」を含む文書は全部返って来てほしい(ここではそういうことにする)。ところがトークナイザーに形態素解析器を使っていて、「京都」で検索した時に「東京都」はヒットしないような設定にしていたりすることもある。こういう時、その「東京都」の文書は検索結果から漏れてしまうので、再現率を下げてしまうことになる。<br />
再現率は、「検索結果で返ってくる<strong>べき</strong>物のうち、実際に検索エンジンが返した物の割合」。</p>
<h3 id="section-2">適合率</h3>
<p>上で「返す<strong>べき</strong>物」という話をしたので引き続きそれを使うと、「実際に返って来た検索結果のうち、返すべきだった物の割合」が適合率。<br />
再現率を上げるためには、例えば登録している文書すべてを検索結果として返すことができる。そうすると「返すべき物」は全て含まれているので、再現率は100%になる。しかし、この結果はいらない物を多量に含んでいるだろうから、期待する結果からは遠い。この時適合率は低いということになる。</p>
<h3 id="section-3">精度</h3>
<p>さて、検索エンジンの精度の話をする時、この再現率と適合率が、よく指標として使われる。最初に上げた参加者みんなの精度のイメージのうち「ユーザーの満足度」「思った物が探せてる」はふわっとしてるけど、再現率と適合率は数値化できるので議論しやすい。</p>
<p>ところが、一般に、どちらかを上げるとどちらかは下がってしまう(らしい)。<br />
特許検索はふつう、適合率を下げても再現率が重要になる。特許出願を考える際には既存の特許とかぶらないことを確認する必要があるが、その時には、漏れがあっては困るからだ。</p>
<h3 id="section-4">現実の話</h3>
<p>再現率と適合率はよく指標として使われるが、現実的には、この二つだけで<del>精度</del><ins>検索結果のよし悪し</ins>を議論できるわけではない。</p>
<p>例えば検索して、結果が1000件あったとする。この時、ふつう、1000件全部は確認しない。全部を確認しないのであれば、「全体に対しての割合」である再現率や適合率は、その厳密な数値には意味がないことになる。<br />
Googleでヒットした時に大事なのは、多くの場合1ページ目、せいぜい3ページ目くらいまでで、みんなそれを目指してSEOを頑張っていた。なので、4ページ目以降は、不要な検索結果ばかりで適合率が低かったとしても問題ない。</p>
<p>もちろんこれはGoogle検索はそういう選択をした、またウェブの一般的な検索はそれでいいことが多いだろうということで、ケースバイケースである。</p>
<h2 id="deldelinsins"><del>精度を高める</del><ins>よい検索結果を提示する</ins>には</h2>
<p><ins>追記。始め「精度を高める」という書き方で書いていた。しかし、他の人のまとめ(前半、講師の<a href="https://twitter.com/ktou">@ktou</a>さんが説明をして、後半で各々自分の言葉でまとめるという勉強会。この日記もそのまとめとして書いている)を聞いているうち、これがよくない表現だと気付いた。精度はやっぱり再現率と適合率のことで、この節で話したいのは「再現率と適合率を考えるだけではユーザによい検索結果を提示することはできない」ということだからだ。</ins></p>
<p><del>精度を上げる</del><ins>ユーザーによい検索結果を提示する</ins>にはどうすればいいか、方法は一概には言えない。ユースケースごとに求められる精度のタイプが変わるからだ。</p>
<p>上で見たような「上位n検の結果だけが特に重要」みたいなケースではスコア付けが大事になる。スコア付けには(昔の)GoogleのPageRankや、ほかにTF、TS・IDF、BM25などがある。それぞれが何なのかは省略する。</p>
<p>確かPageRankは、各ドキュメントの被リンクの数などをスコア付けに使っていたと思う。それが「いい結果」ということでGoogleが受け入れられていった一因になっていた。 <br />
これはTF・IDFなどの、文書中のキーワードから導出した指標の限界を示唆している。<a href="https://twitter.com/ktou">@ktou</a>さんはこのように、キーワードだけを使うのでは、精度はそれほどよくならないと思っているらしい。それよりも文書のメタデータを使ってスコアを考えるほうが現実的にいい結果が得られることが多い。</p>
<p>メタデータには例えばタグがある。<br />
TF・IDFでは、「文書中のTFが高いつまりある単語が多いという時、その単語はその文書を特徴づけている」と考えている。IDFについては、「ある語がたくさんの文書に含まれているほど、その語は文書の特徴を表現していない」と考える。つまり「その文書の特徴は何なのか」ということが知りたい、その知りたいことの導出に文書中のキーワードを利用していることになる。本質的には文書の特徴が知りたいだけなので、書き手の用意したタグというのは、多くの場合キーワード由来の指標よりも、その特徴を顕著に表していることになるだろう。そうしたわけでタグは、検索結果の重み付けに使う指標として有効と考えられる。</p>
<p>他に位置情報も考えられる。渋谷を歩いててラーメンを食べたくなった時に、「ラーメン」と検索すると、渋谷のお店を扱った文書が出てきてほしい。仮に文書中に店舗名しかなくて、「渋谷」みたいな場所を示すキーワードがないとする。でも文書のメタデータに渋谷の経緯度があるとすると、それを利用して渋谷の店を検索結果の上位に出すことができる。</p>
<p>こういった文書のメタデータの他に、検索する人の情報を使うこともできる。今、Googleの検索結果は、(ログインしていれば)訪問した回数の多いページが上位になるようになっている。フェイスブックでの検索も、自分に関連のあるユーザーなんかが結果の上位に来るようになっている。上のラーメンの例でも、スマホからGPSによる位置情報を検索クエリーに乗せて使うことができる。</p>
<p>こういう風に、<del>精度を高める</del><ins>ユーザーによい検索結果を提示する</ins>には、文書中のキーワードを使うだけでは、限界がありそうだ。</p>
<h2 id="groonga">Groongaでのスコア付け</h2>
<p>Groongaでは以下のスコア付けの方法が用意されている</p>
<ul>
<li>TF</li>
<li>TF・IDF</li>
<li>TFで、リミットありの物。TFは参考情報に使って、メインの重み付けの指標は別にあるといった場合。TFは文書の内容に応じて無限に増えていく(例えばヒットしやすい語を埋め込みまくった記事とか)ので、ある単語を書きまくるというスパムの餌食になりやすい。「その語を含んでいる」という事実は考慮しつつも、その影響を一定範囲に抑えるために、重みのリミットを、例えば2(数値はユーザーが決める)とかにしてしまうやり方。Groongaに特徴的で、<a href="https://twitter.com/ktou">@ktou</a>さんはこの重み付け方法を持っている全文検索エンジンを他に知らないとのこと(ちょっと自身なさそうだったw)。</li>
<li>メタデータに重みを付けてスコアに反映させるやり方(以下で説明)</li>
</ul>
<p>最後のは例えば、居酒屋のテーブルがあったとして、</p>
<ul>
<li>居酒屋A … 重みは海鮮:10</li>
<li>居酒屋B … 海鮮:5</li>
<li>居酒屋C … 中華:100</li>
</ul>
<p>たいなメタデータを入れておく。<br />
グルメサイトで「海鮮が美味しいお店の中から探す」みたいなフィルタリングをしている時は、どんなにその値が高くても、居酒屋Cは上位には出てこないだろう。その重みは海鮮ではなくて中華なのだから。</p>
<p>と、今日はここまで。</p>
<p>参加者の一人から「では、他製品と競争するにはメタデータの扱いが重要になるのか」という質問が出ていて、鋭いなあと感心した。</p>
<p>あと、自分の理解のまとめとしてこの日記を書いている間に、すごい面白そうな話が後ろで繰り広げられていたけど、書くのに忙しくて聞けなかった……。</p>
<h2 id="tfidfandor">TF・IDFはAND検索、OR検索で使われる</h2>
<p>追記。<a href="http://flow2flow.hatenablog.jp/entry/2015/11/06/220623">参加者の一人のまとめ</a>発表で、「TF・IDFはAND検索、OR検索で使われる」といった物があった。まとめを書いている時に出た疑問を<a href="https://twitter.com/ktou">@ktou</a>さんに質問して、そのフィードバックを入れたらしい。</p>
<p>これはぱっと見た時にすぐ意味が頭に入ってこなかったが、分かったらもやもやがすごいすっきりした。</p>
<p>TFは分かるけどTF・IDFの話が全文検索で出てくるのがぴんとこなくてもやもやしていた。僕がTF・IDFを知ったのは全文検索ではなくて類似文書検索、なので定義やその意味を聞かれれば一応答えられるけど、それが全文検索でどういう意味を持つのか、と聞かれると詰まってしまう(はずだとこの時に気付いた)。</p>
<p>IDFを考える気持ちは、ある文書とある文書を比べる時に「その二つに共通の特徴でも、そもそも全文書、多数の文書に共通の物は、さほど大事な特徴ではない」ということだ。二つ(以上)の文書を比べる時に出てくる概念なのである。でも全文検索では、ヒットした文書同士を比べるようなことはしないなのにDF・IDFを使っているのでもやもやしていた。</p>
<p>これがAND検索、OR検索のことを考えると、つまりクエリーが複数あって、それぞれについてヒットする文書群があって、その文書群の文書について何らかのスコア付けをする、ということになる。そう考えるとTF・IDFを使うのは自然に思えた。</p>
<p>またこういう風にも捉えられる。AND検索にせよOR検索にせよ、クエリーを「複数の単語からなるごく短い文書」だと考えられる。この「ごく短い文書」に似ている文書というのが、すなわち検索結果の文書なのだ。やはり、類似文書検索で使われる手法を使うことは、自然に思える。</p>
<p>他の人の(ちゃんと疑問を疑問と認識できる)視点で出てきたフィードバックがその場でシェアされるの、ありがたいことである。</p>
<h2 id="bm25">BM25</h2>
<p>追記二。(この日記を含む)みんなのまとめの発表が終わった後、解説が省かれていたBM25というスコア付けの方法も説明してもらった。</p>
<p>BM25はTFとIDFのほかに、文書の長さも考慮する指標。ある単語について「長い文書の中にたくさんあるとして、それはそういうものだろう。短い文書の中にたくさんあるようだと、それはその文書を特徴付けているているキーワードだろう」という考え方で計算する物らしい。<br />
これはある種のスパムブログなんかを弾くことができる。スパムブログで「これ入れとけば検索上位になるっぽい」という単語をひたすら書き連ねているようなやつ、それは文書自体も長くなるので、その分スコアが下がる。</p>
<p>ただ計算量は増えて、計算量としては「TF < TF・IDF < BM25」という順番になっている。</p>
https://diary.kitaitimakoto.net/2015/11/06.html
全文検索の精度とスコアについて
2015-11-06T00:00:00Z
2015-11-06T00:00:00Z
<p>Polymer 0.5、0.8の頃は、Polymerでマテリアルデザインするのが(少なくともその取っ掛かりは)簡単だったように思うけど、1.0になって難しくなったと感じていた。0.8でも1.0でも同様に、<a href="https://elements.polymer-project.org/browse?package=paper-elements">Paper Elements</a>というコンポーネントセット(<code>paper-*</code>というタグ集)が予め用意されていて、それを使うとマテリアルデザインが始められるようになっているのに、どこが違うんだろう。</p>
<p>というところでググったりしていて思い付いたのは、チュートリアルがないからだ。Polymer 1.0自体のチュートリアルはある(<a href="https://www.polymer-project.org/1.0/docs/start/quick-tour.html">Quick tour of Polymer</a>)。でもこれは、Polymerを使って自分で要素を作り、それを使うというチュートリアルだ。名前もチュートリアルではなくクイックツアーになっている。他に公式サイトで探して見付かるのはPaper Elementsカタログの各要素のサンプルと、あとは<a href="https://developers.google.com/web/tools/polymer-starter-kit/">Polymer Starter Kit</a>くらい。前者はリファレンスなので全体の考え方が掴みにくいし、後者はあっさりしすぎている。</p>
<p>でもPolymer 0.5は、自分でPaper Elementsを使いながら一つのウェブアプリケーションを作るチュートリアルがあった(<a href="https://www.polymer-project.org/0.5/docs/start/tutorial/intro.html">Getting the starter project</a>)。これをやっていたので0.5でPaper Elementsを使ってページを作るのに、細々したところはともかく、「全体としてはこういう流れてやるんだな」というところに躓いた憶えがなかったのだ(と、いうほど、使っていないけれど)。</p>
<p>日記をこのGitHub Pages+Middlemanにするにあたって、フレームワークにPolymerを選んだところで改めて探して、ようやく、Paper Elementsの使い方にふさわしい公式ドキュメントを見付けた。</p>
<p><a href="https://elements.polymer-project.org/guides/responsive-material-design-layouts">Responsive Material Design layouts</a>だ。</p>
<p>細かいステップを通じて、レイアウトする時の「枠」を教えてくれる。各ステップでデモンストレーションも用意されているので分かりやすい。おすすめ。</p>
<p>Polymerの公式サイトを探しているのではだめで、<a href="https://elements.polymer-project.org">Element Catalog</a>の下の方にあるガイドを探すべきだった。上のドキュメントから参照している<a href="https://elements.polymer-project.org/guides/flex-layout">Flexbox layout with iron-flex-layout</a>もほぼ必読のドキュメントだと言っていいと思う。まだ読んでいないけど<a href="https://elements.polymer-project.org/guides/using-neon-animations">neon-animation</a>も気になっている。確か今策定中のWeb Animationsを使う要素のコレクションだったと思う。</p>
https://diary.kitaitimakoto.net/2015/11/04.html
Polymerでマテリアルデザインする時に読むといい物
2015-11-04T00:00:00Z
2015-11-04T00:00:00Z
<p>Rubyでは、文字列(<code>String</code>)クラスに<a href="http://ref.xaio.jp/ruby/classes/string/length"><code>length</code></a>というメソッドがあって、これは文字列の長さを返してくれる。「文字列の長さ」というのは一体何なのだ、というのは実は自明ではない(「実は」とか言ってみたけど、みんな、知っている気がするな)。文字の数かも知れないし、バイト(オクテット)の長さかも知れない。Rubyの<code>String#length</code>の場合は、文字の数を返す。バイト数が欲しければ<a href="http://ref.xaio.jp/ruby/classes/string/bytesize"><code>bytesize</code></a>メソッドを使う。</p>
<p>余談だけど、Rubyで文字を扱おうと思ったら、るびまの<a href="http://magazine.rubyist.net/?0025-Ruby19_m17n">Ruby M17N の設計と実装</a>をぜひ読んだほうがいい。</p>
<p>さて長さに戻って、文字の長さというのは、例えばこういうことだ。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># coding: utf-8</span>
<span class="c1"># ソースコードファイルのエンコーディングをUTF-8とする</span>
<span class="s2">"a"</span><span class="p">.</span><span class="nf">length</span> <span class="c1">#=> 1</span>
<span class="s2">"a"</span><span class="p">.</span><span class="nf">bytesize</span> <span class="c1">#=> 1</span>
<span class="s2">"あいう"</span><span class="p">.</span><span class="nf">length</span> <span class="c1">#=> 3 三文字の文字列</span>
<span class="s2">"あいう"</span><span class="p">.</span><span class="nf">bytesize</span> <span class="c1">#=> 9 ソースコードファイルがUTF-8なので、文字列リテラルもUTF-8になり、バイト数は9になる</span>
<span class="s2">"あいう"</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="s2">"UTF-16LE"</span><span class="p">).</span><span class="nf">length</span> <span class="c1">#=> 3</span>
<span class="s2">"あいう"</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="s2">"UTF-16LE"</span><span class="p">).</span><span class="nf">bytesize</span> <span class="c1">#=> 6</span>
<span class="c1"># UTF-16でエンディアンを明示しない場合は、(ファイルではなく文字列オブジェクト自体に)2バイトのBOMが付く</span>
<span class="s2">"あいう"</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="s2">"UTF-16"</span><span class="p">).</span><span class="nf">length</span> <span class="c1">#=> 4</span>
<span class="s2">"あいう"</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="s2">"UTF-16"</span><span class="p">).</span><span class="nf">bytesize</span> <span class="c1">#=> 8</span>
</code></pre></div></div>
<p>(今どき断らなくていいとは思うけど、ここでは1バイトは1オクテット=8ビット)</p>
<p>UTF-16にはサロゲートペアという物があって、多くの文字は一文字あたり16ビット(2バイト)なんだけど、サロゲートペアを使って表す文字は一文字表すのに32ビット(4バイト)使う。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"𩸽"</span><span class="p">.</span><span class="nf">bytesize</span> <span class="c1">#=> 4</span>
</code></pre></div></div>
<p>それでも、Rubyはこれを「一文字」として数えてくれる。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"𩸽"</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="s1">'UTF-16LE'</span><span class="p">).</span><span class="nf">length</span> <span class="c1">#=> 1</span>
</code></pre></div></div>
<p>人間にとってとても分かり易い。</p>
<p>JavaScriptではこれは「長さ」が2となるらしい(<a href="http://qiita.com/YusukeHirao/items/2f0fb8d5bbb981101be0#iii-ii-%E6%96%87%E5%AD%97%E5%88%97%E9%95%B7%E3%82%92%E6%AD%A3%E3%81%97%E3%81%8F%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">JavaScriptでのサロゲートペア文字列のメモ</a>)。16ビットが何個分か、という数え方のようだ。JavaScritpでは内部エンコーディングがUTF-16らしいから、処理系の設計者にとってこれが自然だったんだろう。</p>
<p>ここまでなら、Rubyは人間に優しい言語ですね、よかったよかった、となる。しかしたまに困ることがある。</p>
<p>この前気まぐれで、<a href="https://github.com/KitaitiMakoto/nokogiri-xml-range">Nokogiri::XML::Range</a>というRubyGemを作った。これは、ブラウザーのマウスで選択した部分を表したりする時に使う<a href="https://dom.spec.whatwg.org/#ranges">DOM Range</a>という仕様を、<a href="http://www.nokogiri.org/">Nokogiri</a>を使って実装してみた物だ。</p>
<p>これを書く時に、文字列の「長さ」を扱う必要があった。長さとは一体何なのか、仕様書の中を探していくと</p>
<blockquote>
<p>The length attribute must return the number of code units in data.<br />
(length属性はデータのcode unitの数を返す)</p>
</blockquote>
<p>という表現に行き着く(<a href="https://dom.spec.whatwg.org/#dom-characterdata-length">https://dom.spec.whatwg.org/#dom-characterdata-length</a>)。</p>
<p>更に、この「code unit」のリンクを踏むと、</p>
<blockquote>
<p>The value of the string token is the sequence of 16 bit unsigned integer code units (hereafter referred to just as code units) corresponding to the UTF-16 encoding of S.<br />
(文字列トークンの値は、文字列SのUTF-16エンコーディングに対応する16ビット符号なし整数のcode unitの列(以後、単にcode unitとする)である)</p>
</blockquote>
<p>という表現が現れる(<a href="https://heycam.github.io/webidl/#dfn-code-unit">https://heycam.github.io/webidl/#dfn-code-unit</a>)。ここだけ切り取って翻訳するのは僕には難しかったので、できれば前後まとめて読んでほしいけど、要は「16ビットが何個あるか」を文字列の「長さ」とする、ということだ。UTF-16では多くの場合一文字が16ビットで表現されるので、この長さは直感と一致する。でもさっきの「𩸽(ほっけ、らしい)」の場合は32ビットなので、一文字でも「長さ」は2になる。</p>
<p>どうもUnicodeか何かの規格でも、「UTF-16 length」という物が定義されていて、ここで言う「長さ」と同様の物らしい。正直あんま調べる気の起きないところなので教えてもらったツイートをそのまま貼る:</p>
<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/KitaitiMakoto">@KitaitiMakoto</a> ですね。サロゲ以外にも合成文字とかもあります。utf-16にエンコードしたときの2バイト単位の長さと考えるば良いかと。</p>— OE Waku (@wakufactory) <a href="https://twitter.com/wakufactory/status/661340825687752704">2015, 11月 3</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Nokogiri::XML::Rangeで扱う対象はNokogiriで扱う対象なので、文字エンコーディングが何になるかは分からない、決め打ちできない。その前提で、途中で「長さ」を扱うために、一旦UTF-16に変換して長さを数える、という処理を入れざるを得なかった。多分、この「長さ」は実際には人間の感じる一文字、つまりRubyの<code>String#length</code>の値にしても殆どの場合問題ないだろうなと思いつつ、ライブラリーなのでそうではない場合も一応扱えないといけない、ということでパフォーマンスが落ちるの覚悟でこんなことをしないといけないのはもやもやした。</p>
<p>もう一つ困ったことがある。<a href="http://www.idpf.org/epub/linking/cfi/epub-cfi.html">EPUB CFI</a>の仕様でも、文字を数えるのに「UTF-16 length」を扱うことだ(上のツイートの「UTF-16 length」というのはこの仕様の表現を使った)。</p>
<p>EPUB CFIを非常に大雑把に説明すると、「EPUBファイルの中のある一点、もしくはある範囲を表現する物」だ。EPUBの読む部分は多くの場合XHTMLになっているので、テキスト中のある一点(一文字)を指す場合には、「DOMツリー中の親要素までのパス+文字オフセット」という物を使うことになる。例えばこういう風な見た目をしている。</p>
<pre><code>book.epub#epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)
</code></pre>
<p>全体の意味を知りたい場合は仕様なり解説記事なりを読んでほしいけど、最後の「<code>:10</code>」というのが文字オフセットの部分だ。対象テキストノードの10文字目、ということになる。「文字目」と言ったが実際にはUTF-16 lengthなので、人間的な感覚の文字数とは限らない。</p>
<p>EPUB CFIは表現の仕様であって、用途について決まった物があるわけではないけど、例えば、ウェブページのURIのフラグメントのように、文書の途中にリンクを貼る場合に使うことができる。このEPUB CFIを渡してやると、EPUBリーダーがその部分を頭出しして開いてくれる、というのは普通に期待される使い方だ(実際、<a href="http://bibi.epub.link/">BiB/i</a>というEPUBリーダーはこれに対応している)。</p>
<p>JavaScriptでこれを扱うなら(或いはJavaも?)簡単なんだろうけど、Rubyだとやはり不必要に思われる処理を入れないといけない。せっかく人間に優しく出来ているのに、仕様のほうがそうなってなかった(いや、UTF-16で暮らしてる人にはフレンドリーなんだろうけどね)。まあ技術文書なので、そういうもんなんだろうけど、愚痴りたくもなりますね。</p>
https://diary.kitaitimakoto.net/2015/11/03.html
Rubyでの文字列の「長さ」
2015-11-03T00:00:00Z
2015-11-03T00:00:00Z
<p><a href="http://sqale.jp/">Sqale</a>+<a href="http://www.tdiary.org/">tDiary</a>で書いていた<a href="http://apehuci-kitaitimakoto.sqale.jp/apehuci/">前の日記</a>から、<a href="https://pages.github.com/">GitHub Pages</a>+<a href="https://middlemanapp.com/jp/basics/blogging/">Middleman-Blog</a>に移行を始めた。</p>
<p><a href="http://hatenablog.com/">はてなブログ</a>なんかのサービスにしようか、迷ったけれど結局静的サイトジェネレーターを選んだ。
サービスだと、更に移行する時にコンテンツを引き継ぐのが面倒そうだったからだ。あと、やるかやらないか分からないけど、というか、今までのtDiaryの運用を見ているとやらなさそうではあるけど、JavaScriptとか自由に使って遊べるほうがいいかな、というくらい。</p>
<p>GitHub Pages使うとGitを使うわけで、携帯とかから更新できなくて時代に逆行している感じがあるけど、まあ、まあ。Werckerとか使って、特定のブランチで書いたら(そこまではGitHub上のエディターでできる)自動で更新、とかやってもいいしね、必要になったら。</p>
https://diary.kitaitimakoto.net/2015/11/01.html
日記を移行し始めた
2015-11-01T00:00:00Z
2015-11-01T00:00:00Z
2020-05-03T00:00:00Z