My Browser Profile Chooser 3.0 released!

My Browser Profile Chooserが3.0になった

1.x, 2.xではZshスクリプトだったが、Rubyスクリプトに変更され、プロファイル設定は2.xのZsh関数として定義する形からYAMLで書く形になった。

3.xでの非互換変更を行った理由だが、設定を書くのが面倒だったからである。 互換性のため、2.x系も別のブランチとして残っている。

コード的にも簡単すぎてあまり見るべきところがないので、今回はリリース報告だけ。

webUIデザイン考

私はかなり前からデザインの仕事もしているし、評判も上々。 日頃デザインの勉強もしているし、自分でも悪くないと思っている。

けれど、デザインに一家言あるみたいなタイプの人からは評判が悪いし、そもそも私のウェブデザインはウェブデザインの常識に則っていない。

とはいえ、私のほうが正しいと主張できるだけの根拠はちゃんとある。 なにより、「本当に正しくデータを取ったか?」である。

なお、ここで出てくる様々な実験データは、なにかから引用しているわけではなく、 私が自分で実験・聴取しているものである。

ほんとかよ、それ

直感的であるよりも経験的である

「ひと目見てわかることよりも、慣例に則っていることのほうが大事」 という主張なのだけど。

絶対ウソだ。

デザインする側は全く分かっていないけれど、 ハンバーガーメニューってみんな知らないよ

ハンバーガーメニューを理解できるのはそれなりにコンピュータに通じている人であって、 私が話している限りだとスマホネイティブな世代の人を含め、ハンバーガーメニューは無視する人のほうが圧倒的に多く、 「ハンバーガーメニュー」という言葉はまず知らないし、それがメニューボタンだという認識もない。

実際、私が手がけたサイトのログをみる限り、

  • 他のナビゲーション手段があるかどうかに関わらず、ハンバーガーメニューはほぼ押されていない
  • ハンバーガーメニュー経由でしかアクセスできないようになっているものには人がこない
  • 常にハンバーガーメニュー経由でナビゲーションするようになっていると離脱率がものすごく上がる

なので、ハンバーガーメニューはごく一部のわかっている人だけのものだという認識で良いはずだ。

動的表示

HTML5だ、動的表示だ、JQueryだみたいな話は当たり前のようにされるけど。

ユーザーは望んでないぞ、それ。

これに関しては明確な不満が聞かれることは稀だが、「重い」という形での不満になったり、あるいはちゃんと説明すると(容量が大きいことによってどのような影響になるのかといった話)「それはいや」という回答が返ってくる。

ごてごてと重くして、CDNで速くするくらいなら、最初から軽くしておくべきだ。

アニメーションで取られる時間も明確にUXを悪化させており、実際にその仕様が異なるページを体験してもらう実験では、非常に速いアニメーションが最も好まれ、アニメーションを認識できる程度のアニメーションがあるくらいならば一瞬で実行されるほうがずっと良い、ということがわかる。

遅延表示

記事の全文を表示せず、ユーザーアクション、スクロールに従って読み込むのは必須要求であるように言われるのだが、聴取してみると コンピュータの熟達度によらず極めて評判は悪い。

これは、スクロールの判定の問題による。

まずPCではEndキーを押してのスクロールに対応できない。そもそもEndが末尾にならない。 実装方法によっては、PgUp/PgDnのスクロールの判定をしないから、私のようにキーボードでナビゲーションしている人をはじくことになる。

さらにスマートフォンのナビゲーションでも、弾いたり反射させる場合に対応できない。

そもそも、大抵の遅延ロードが、私が普通に読んでいても途中でなんども読むのを止めさせられることになる。 「続きを読む」も何の節約にもならないので大嫌いだ。

この話は人にすると多くの場合強い共感を得られる。 極めて劣悪な体験である。

ちなみに、私は遅延表示するサイトはよほどがない限り、読むのをやめる。

実験と聴取と経験

ハンバーガーメニューは誰も使わない

前述のようにハンバーガーメニューは誰も押さない。

ちゃんと「アイコン+ “メニュー”の文字」になっていると押してもらえる。

英語だと使わない

“MENU”とかいう英語ですらも、英語で書くと使ってもらえなくなる。 ところが、なぜか“HOME”だけは英語で書いても全く影響がない。

back to HOMEに関しては、「トップページ」と書くよりも「HOME」と書いたほうが理解されやすい。

「HOME」と「ホーム」は誤差の範囲。

最近のソシャゲのUIはこのあたりよくわかってる。

アイコンだけだとやはり使わない

UX面で見ればラベルは絶対必要。デザイン的にはイヤ。

アイコンだけでがんばる場合は、それを押す必然性がないといけない。 これはハンバーガーメニューでも同じだし、ハンバーガーメニューはアイコンのデザイン自体が直感的じゃないので、よりハードルは高い。

SEOより情報価値

最近はGoogleがSEOに屈した感があるが、結局情報価値がある内容を書いているとその情報を必要とする人はその情報を頼るしかないので、あまりSEOスペシャルみたいなものは必要ないと考えている。

内容さえあれば、変なことするよりも、人間的にも機械的にも意味がきちんと伝わるように書くのが長期的に見て色々と得だ。

内容が重複する場合は基本的に情報価値はないとみなされるが、もちろんより適切にまとまっているなど情報の扱い方によってもその価値は変わってくる。

結局、SEOのことを気にする時間があるならば、少しでも良い記事を書くために心を砕くことに時間を使うべきだと思う。

明朝体とウェブフォント

「長文には明朝体が良い」というのは、ウェブには全然浸透していない、出版関係の常識だけれど、これに関しては私は特別には同意しない。ゴシック体だから長文が辛い、ということもない、というのが私の意見だし、むしろウェブではみんなゴシック体を見慣れてるからゴシック体のほうがいいんじゃないかとすら思う。

ただ、文章に対する没入という観点からいうと明朝体のほうが良いようだ。

実際に、難しい長文を読むにあたって、どのような書体が好ましいかという実験をしたところ、最も良いのはUDアポロであり、アポロでもフォークでもタイポスでもスキップでもいいからアンチックでしょ、ということになる。 ちなみに、ロゴたいぷゴシックは長文だとやや読みにくい。フリーフォントなら源暎ラテミンの評判が良い。

私はアポロの製品版をまだ持っていないので、今のところこの文章を書くのにはフォークを使っている(でも、ライセンス期限が近づいている…)が、フォークは実はそんなに読みやすくない(アンチックの中では)。とにかくアポロが読みやすい。次いで源暎ラテミン、あるいはラテゴ。元陽逆アンチックも読みやすいと評判。

こうしたゴシック体と明朝体の中間のフォントが最も読みやすく感じられる、というのは、まんま「明朝体のほうが読みやすいと言われればそうだし、ゴシック体が読みづらいと言われてもそうでもない」という感覚に直結しているのだと思う。

より漫画的なアンチック体(例えば源暎アンチック)もかなり読みやすい。そういうものなんだな、と思ったりする。

だから、可能ならウェブだってアンチック体で表示したい。 だが、現実には難しい。

そもそも汎用の指定がserif, sans-serif, monospace, fantasy, cursiveの5つしかない。 せめて丸ゴシックを指定できるようにしてほしいし、アンチック体を指定するなんて夢のまた夢だ。

システムフォントを指定することがどれだけバカバカしいかという話は、多分Chienomi読者なら耳タコだろうし、そんなことは絶対しないと私は信じている。

しかし日本語ウェブフォントなんていうのは死すべしである。大きくても100kBもない欧文フォントなら、まぁちょっと大きい画像1枚くらいか、許そうとなるが、源暎ラテミンなんて9MBもある。問答無用で動画ねじ込まれるようなもんである。通信容量は有料のものだし、人の通信容量を貪るんじゃない、となる。

だから制作者がコントロールできる部分ではないが、どちらかといえば長文読みの場合は明朝体のほうが良いのではないだろうか。 Androidでは明朝体が出ないということも踏まえた上で。

リーダービュー

リーディングという意味ではリーダービューはかなり理想的な状態であるように思われる。

ウェブデザイナたちがこれがユーザービリティだとごてごてしたデザインを押し付けた結果、CSSを除去し、本文だけを抽出する機能がブラウザに搭載されたというのは本当に笑える話だが。

しかし、リーダービューは私が聞き取った限りで使用しているという人を見つけることはできなかった。 「知っている」という人は3割強。恐らくこの値は全体比からすればかなり高い。

リーダービューを前提にすることができれば、ウェブデザインは可読性よりもナビゲーションのしやすさに寄せることができるが、そうではないのでリーダービューのような可読性を目指すことになるだろう。

ちなみに、私のサイト(PureBuilder Simplyで書かれているもの)はいずれも綺麗に書いてあるのでリーダービューが効く場合は非常に読みやすくなるが、「はるかのおはなしのおはなし」を除けば元がリーダービューに近い見た目をしているため、効果はあまりない。 一方、このChienomiはWordPressなので、リーダービューを使うとだいぶゴミを感じるが、余計なものが消えて読みやすくなる。

my designs

Mimir Yokohama

Mimir Yokohamaのデザイン自体は、もともとがWordPressページであり、なおかつWordPressのデザインを維持することからスタートしているため、基本的な形式はWordPressのデザインに準じている。

中央寄せコンテンツコンテナのレスポンシブ2カラムというのは、典型的なブログデザインといっていいだろう。

ただし、現在はしっかりと私のウェブサイトとしての工夫も盛り込まれたものだ。

Mimir Yokohamaのウェブサイトは、リテラシーが低い人が読む、という前提があるため、ユーザーに何かをさせるということは回避する方向である。知識がなく受動的であっても使えるようにという工夫だ。

基本的なところでは、Mimir Yokohamaは「経験則によってナビゲーションできるようにすべき」という主張を受け入れたものになっている。 だが、構造的スマートさよりもアクセシビリティを優先し、同じナビゲーションが複数配置される構造だ。

ハンバーガーメニューの中身はメニューとTOCだが、これはサイドカラムにも搭載されている。重複させることでその人の直感がどちらに属してもカバーできるようにしている。 では重複は2箇所かというと、実は3箇所である。TOCは記事よりも手前にも存在し、ナローモードでは下に行くサイドカラムではなく、記事手前に表示されるようになっている。

ロゴクリックでのback to homeや、破線アンダーラインでの辞書機能、lightboxなどは経験則的機能だ。 その人のブラウジング経験によってはより便利に使えるわけである。

その他、本文を含めてとても説明的。 どんなに丁寧に書いても読まない人は読まないのだが、デザイン的にはちゃんと説明されるべきだ。 そういえば都営地下鉄の駅の表示に情報が欠落していて、結局わざわざ説明書きの張り紙をした、なんて話もあった。

はるかのおはなしのおはなし

はるかのおはなしのおはなしはウェブ縦書きのサイトである。 世間的には、ほぼエロゲーの話をするサイトとして扱われている。

これはいくつかの実験を含んでおり、縦書きはその一部である。

もうひとつ大きいのはスケーラブルフォントサイズだろう。 文字サイズがウィンドウサイズに依存しており、ウィンドウを大きくすることで大きな表示で読めるようになっている。

ただ、ブラウザの縦書き実装が相当にあまりて、ウィンドウリサイズにちゃんと対応しておらず、リサイズ時はリロードする必要があるとか、場合によってはカラム境界がおかしいなどの問題がある。 Android版Firefoxに至っては、以前はちゃんと表示できていたのに、今は完全に崩壊してしまっている

正直なところ、私はあまり読みやすいとは思っていない。多分、JavaScriptでスクロールディレクションを変換し、行方向を20emなり30emなりに制限するほうが読みやすい。そして縦書きが別に読みやすくはないというのも感じてしまう。 これはそもそもブラウザの縦書きエクスペリエンスがだいぶ悪いということに起因している。

リーダービューにすると横書きにできるのですごく読みやすくなる。

なるべく色々がんばろうとはしていて、例えば

  • 小説のように読ませたいというのがあるので、ナビゲーションは意識に入らないようになっている。逆にナビゲーションしたいときに困るかもしれないが
  • 電子書籍のような「めくり」に対応
  • 日本語縦書きプロポーショナルメトリクスを採用

と、技術的にものすごくアグレッシヴ。商用サイトでは絶対できない。

実は「はるかのおはなしのおはなし」は布石であり、実験であった。

Harukamy’s Memoranda

Harukamy’s MemorandaはもともとJournal de Akiだったものである。 その後、ここと分離してReason Essembleになって、リニューアルで一部分を残して新規サイトになった。

レイアウト自体はいまさら驚くものではない。構造的には4カラム構成であり、これは現代的というよりは、古のフレームレイアウトに近い。 ナローウィンドウにおいては直列になる。

今さら見たことはないという人も多いかもしれない。

画面レイアウト上は昔っぽいが、中身としてはモダンに普通である。 フッターをfixedにして、コンテンツコンテナを全幅にするだけである。

しかし、その配置に関してはちょっと特殊だ。

サイドカラムはあくまでメニューナビゲーションに過ぎない。 現代的にはこれは上部に持ってくるものである。だが、上部に持ってきてしまうと実際には幅に対する制限がより厳しくなるので、サイドに持ってきた。

“Mimir Yokohama”はナビゲーションをサイドカラムとモーダルウィンドウから選択でき、「はるかのおはなしのおはなし」は常にモーダルウィンドウでナビゲーションを行うことで画面上からナビゲーションを追い出したが、“Harukamy’s Memoranda”では逆にモーダルウィンドウを持っていない。ナビゲーションはメインカラム上に存在し、ナビゲーションボタンとしてそれらへのリンクがある。

内部リンクは元位置に戻るのが戻るボタンで処理できるので非常にユーザビリティが高い。

見た目はちょっと不格好だが、恐らくこれが最善。 むしろ情報をみるために任意位置までスクロールしなければならないサイドカラムへの記載のユーザビリティが低すぎる。

本文に関してはほぼ「なり」であり、コンテンツ幅が狭いほうがよければウィンドウをタイルすれば良いじゃない、という考え方になっている。ユーザーの意思に関係なく、ディスプレイの空間を無駄に使って一部分だけに目を集中ざて読ませるようなものは嫌いだ。 ただ、ちょっとこだわりがあって欧文ウェブフォントになっている。これは、タイトルロゴにもサイドカラムにも画像は使っていないし、特にサイドカラムはかなりコンデンスドなフォントである必要があるといった都合による。 このサイトは割とデザインコンシャスなので、本文フォントの欧文はGildaでウェブフォント。

ただ、ちょっと思ったのがChromium系だと内部リンクをクリックしたときにウェブフォントを取りに行っちゃう。 これはちょっとおかしい。

また、フォントに関してはある程度のリテラシーを期待して「serifにしてあるから自分のブラウザを設定してよ」という形。 設定しないままだと少し困ってしまう等幅フォントだけは軽く指定してある。

長文読みに適するようにserif指定であり、赤めの低コントラストになっている。 このあたりは長年長文読みを研究してきた成果をぶちこんだ感じ。

デザインの話はAboutのページ内で解説している

Reasonset

コンテンツはまだないが、ついにリニューアルされたReasonset

Reasonsetとしては第三世代となり、私のメインウェブサイトとしては9代目になる。

これは、私が考える参照性がない長文読みのための、現在のベストだ。 そして、それは「はるかのおはなしのおはなし」と“Harukamy’s Memoranda”を組み合わせたものになっている。

コンテンツレイアウトはウィンドウ幅によらず直列である。 ナビゲーションも含めてすべて直列なセクション上にあり、“Harukamy’s Memonranda”からさらに1つ減った2つの内部リンクナビゲーションボタンがアクセスを提供する。

これは“Harukamy’s Memoranda”を発展させたものだが、フォントサイズの指定は「はるかのおはなしのおはなし」同様、ウィンドウサイズに基づくスケーラブルである。 こちらもさらに発展させており、最小16pxでシームレスにスケールする。

基本的には縦長にしたときのほうが情報量は増えるので、表示を絞りたい人は横長、ざっと読みたい人は縦長と使い分けられる。 もちろん、ブラウザウィンドウの空間は余すことなく使える。

内容に参照性がある場合は、一覧性が重要になってくるのでこのような表示は好ましいものではないが、Reasonsetの場合は先頭からじっくり読むタイプの記事に限られるため、この形式にした。 このようなデザインを採用するとしたら、コラム程度では甘く、かなりじっくり読める小説サイトのようなものに限られるだろう。

フォント指定も完全にgenericなものに限られ、WINGでホストしているので“Harukamy’s Memoranda”以上に軽く、速い。 フォントは全面的にSerif指定で明朝体。

要はリーダービューに近い、それ以上に「読む」ことを求めたものだ。軽めに読めるブログと違い、じっくりしっかり読むことを求めるスタイルでもある。 コントラストは青系でやや低めになっており、任意にダークテーマも選択可能。ダークテーマになるとコントラストはさらに下がる。

「事前生成戦略」の原点、チャットスクリプト (コードつき)

チャットの開発、そして事前生成戦略に到達する下りはなんども書いているが、それが非常に革命的で、「極めてシンプルにすることができた」という話はそれだけではピンとこないのが普通だろうから、コードを添えて解決することにする。

事前生成戦略

PureBuilder Simplyで採用されている「事前生成戦略」は、私にとってはウェブアプリケーション開発におけるウリのひとつになっている。

その内容は、基本的に「ユーザーが要求するものは静的HTMLであるようにする」ということである。

キッカケはNginxだった。 2005年にはもう取り組み始めていたのだから、かなり感度は高かったといえるだろう。

当時、私のwebサーバーはApache 1.3だったが、結局はこのあとLighttpdへの移行することになる。 そしてその後はまさかのDeleGateへと先祖返りする。

だが、Nginxを検討したのは「静的ファイルの応答速度を重視している」という設計のためだ。

2005年には、ADSLの普及により回線速度がだいぶ向上していた。結果的に、従来では考えられないくらいチャットに対するリクエストが増大するという問題が発生していた。 ちなみに、当時のHTMLチャットといえば、「みんなでアクセスするとエラーになるもの」であり、ログ表示がおかしかったり、発言が消えたり、ページが取得できなかったりということは日常的にあった。

だが、そもそもwebサーバーは静的なページを配信するのが最も基本であり、静的なページにすることで多くの問題が解決するものと思われた。 そもそも、静的なページを取得するのであれば、CGIスクリプトは起動されず、負荷はとても少ない。 また、セキュリティ上のリスクなども(webサーバーが自分の責任で保守しているのではないなら)考えなくて良い。

少なくとも、静的ページを配信することはセキュリティ上もパフォーマンス上も良いことであるのは確かだ。

チャットで事前生成戦略

そもそも、チャットで問題が起きる理由は、「新しい発言を取得しようとしてリロードボタンを連打するから」であり、一種のF5アタックを仕掛けることになるからだ。

仮にF5アタックがさけられないとするならば、更新されるものが静的HTMLであればスクリプトが呼ばれることはなく、随分軽くなる。

私はそもそも、チャットスクリプトは随分長く開発に取り組んでいた。 その目的は既存のフリースクリプトを利用していた状況からの脱却であり、私がプログラミングを再開したときにまず行ったのは、チャットスクリプトを改造すること、そして学習の一環としてそれら(特にPerl4で書かれているもの)をモダンに書き換えることだった。

私のサイトでメインで使われてきたチャットスクリプトは「ゆいちゃっと2000」のち「TeaChat」であるが、サイト全体では非常に多くのチャットスクリプトを動作させていた。 フリースクリプトを比較すると同時に、私の開発テストでもあった。 舞台となったのはfreeweb、そしてロリポップ!である。

プログラミング言語を学習する過程でも、「その言語に適した形でチャットスクリプトを再実装する」という目標があり、 制作した「TeaChatのクローンスクリプト」は、Perl5(オブジェクト指向版), Ruby 1.8, PHP5, Python 2.4となかなか多い。

TeaChatのクローンスクリプトを書く中で感じたのは、 「チャットスクリプトは結局のところ、発言があったときはログを更新するという操作があるが、そうでないときはログを読んでHTMLをビルドしているだけである」ということだ。

この気持ちがより強くなったのは、eRubyを学んだときだった。 チャットのページは、eRubyエンジン、eRubyテンプレート、ログファイルがあれば生成可能であり、Rubyスクリプトは必要としない。 だが、TeaChatに準じると、わざわざRubyスクリプトであれやこれやしてからeRubyエンジンを呼び出すことになる。

そして、何度も何度もリロードによってHTMLをビルドすることになるのだが、更新するのは発言するときだけであり、ビルドする頻度に対して更新する頻度は極めて低い。 更新されていなければ、全く同じ内容のページをビルドしているわけである。これは、この上ないほど無駄だ。

だったら、更新があったときだけHTMLを更新して、ユーザーはHTMLを読めばよいではないか。

結果的には、フレームだけでなく、発言用ページも、チャットページも全て静的HTMLという設計ができあがった。 唯一、発言フォームのsubmit先がCGIであり、これは当初は静的HTMLそのものを編集する、というものだった。 のちに、eRubyを使ってログから生成する設計に変更される。

具体なコード

当時のコードは残っていないが、GitHubにその概要を実装したものを上げた

TeaChatほど高機能なわけではないが、当時のHTMLチャットとして基本的な機能は備えている。 PerlのチャットCGIは小さなものでも300行程度はあったことを考えると、39行というのは驚異的な小ささである。 しかも、このスクリプトはWebrickサーブレットになっており、サーバー機能すら自分で賄う。

本質部分はわずか8行で、

cgi = req.query
chat.unshift({name: esc(cgi["name"]), timestamp: Time.now.strftime("%y-%m-%d %T"), chat: esc(cgi["chat"])&.[](0, 1024)})
chat = chat[0,30]
File.open("chat.log", "w") do |f|
  Marshal.dump chat, f
end
chat_content = ERB.new(CHAT_HTML).result(binding)
File.open("chat.html", "w") {|f| f.puts chat_content}

となっている。ここでは

  • パラメータを読み込み
  • パラメータをオブジェクト化してチャットログに追加し
  • ログを保存して
  • HTMLにビルドして
  • HTMLファイルとして保存する

という手順である。

リロード間隔は5秒と短いが、手動で連打されるよりははるかにマシなので、手動ではやりにくい仕様になっている。

事前生成戦略をとることでコードが短くなり、バグの余地も減った。 そして、設計自体が単純になり見通しもよくなった。基本的に、良いことづくめである。

事前生成戦略の応用

では、このような事前生成戦略が有効なケースを考えてみる。

掲示板システムなどは、より読むことが多く、より適している。 そのように考えていくと非常に多くのケースに使えるということがわかる。

なによりもすごいのが、ブログのような発信型のコンテンツだ。

このようなものは当時、ページマスタリングシステムと呼ばれていた。 その要点としては、テンプレートがあることでページヘッダなど共通の部分を一箇所にまとめ、コンテンツ部分だけが異なるものを書こう、というようなことだ。

しかし、ブログシステムでこれを使うのは明らかに過剰である。 現在でいえば、Pandocですらそれは叶えられるのだ。

このことから、「テンプレート機能のサポート」「PODのようなより簡単にコンテンツを記述できる言語のサポート」の2点がテーマになった。 前者はPureBuilderとして、後者はPureDocとして実装されることになる。これに「記事の前後関係をもたせる」ことを目標としたのがACCSだ。

これらの大きなメリットとして、事前生成戦略ならではの「軽さ」だけでなく、実装が容易であり、なおかつ安全であるということがいえる。

まず、ログイン機能、管理機能などを実装する必要がない。 単純にファイルとして生成するものなので、「ファイルの編集」という概念に吸収される。 これで実装がとても楽になる。

さらに、webに対する攻撃というのはほとんどが定番webアプリケーションの機能を起動しようとしたり、ログインを試みるものであるから、単なる静的HTMLファイルでは攻撃の起点がない。 非常にセキュアである。

「事前生成戦略」という考え方からすれば、そもそも手元で静的ファイルに変換してしまうPureDoc/PureBuilderはより発展的な考え方であった。 これとは別に、検索ページのようなオンデマンドで提供される必要があるものはこの考え方が通用しない。むしろ、負担になる場合すらある。 そのために、「遅延生成戦略」というものも編み出された。これは、「例えページが更新される状況が起きたとしても、最初にアクセスされるまではページを実際には更新しない」というものである。 ただ、これ自体はほとんど「コンテンツキャッシュ機能」と変わりがなく、それほど独特なものではない。生成するべきかどうかを判断するために、リクエストはスクリプトが受け付けなくてはならないからだ。 実際に遅延生成戦略を取ったアプリとしてはMongrelを用いたサーブレットとして実装されたものがいくつかあるが、キャッシュとして以上の効果は発揮できず、この話を大いにすることはなくなった。

郷愁, 探求, 求道

今にしてみれば意外に思われるかもしれないけれど、私は当初から「変わったコード」「きわどいコード」を書いていたわけではない。 そもそも私は完璧主義者だったし、ものすごくきっちりしたコード、学習時に使用したものを正しく踏まえたコードを書いていた。

だが、当時から好奇心は強かったし、真理の探求という傾向も強かった。チャットスクリプトの再実装を繰り返す中で、「これは本当に必要か?」「こう書いたほうが本質的で端的なのでは」という疑問が次々湧き上がり、 1年くらいは耐えていたが、結局は徐々にアレンジを加えるようになっていった。

「別にアプリケーションで応答する必要はないじゃないか」というところに至るのがその流れだ。 サンプルでは本質部分だけを書くことをテーマにしているが、実際にはTeaChatに存在する機能は全て網羅するものを制作した。 TeaChatには電報機能があり、これはprivate messageである。つまり、「ユーザー固有のチャットを事前生成する」という戦略に成功していたことを意味する。

これは2バージョンあり、最初のバージョンではユーザーごとに固有のHTMLファイルを開くようになっていた。 こちらはやや複雑で、

  • フレームページとしてディスパッチャが呼ばれる
  • ディスパッチャはUIDを生成し、UIDのディレクトリを作成してベースとなるHTMLファイルを出力する
  • フォームにはUID値を覚えておく
  • 当該チャンネル上で一定時間内に発言のあったUIDを覚えておき、そのUIDを更新対象にする

可能な限り漏れがない仕様ではあるのだが、見てわかるとおり完全ではない。 電報リストのためにF5しなければならないというのが最も大きい問題だろうか。

後発の仕様はAjax仕様であり、XMLHttpRequestを使う。 HTML内にメッセージIDが埋め込まれており、そのメッセージIDが未知のものであれば、XMLHttpRequestによって追加で取得する。取得したメッセージはJavaScriptによってタイムスタンプでソートし、チャットに混ぜ込まれる。 HeartComではこれを発展した形式になっており、PM用(というよりuser relem用)のアプリケーションが独立して存在する。そしてXMLHttpRequestによってポーリングを行い、メッセージが受信できるようになっている。

結局このチャットにおける事前生成戦略採用が転機になった。 このスクリプトは、常識や、正しいとされていることとは全く異なる。だが、完全に正しく動作し、意図を達成できる。

至って本質的だ。この事実は「本質は端的に表せる」ということを意味しており、その正誤自体は結果1によって証明できる。 また、正しく動作する限りいずれも正しいのであり、世の中で正しいとされている手法や常識はどちらかといえばそれに依存している。つまり、「正しいが、最適であるかどうかは別」である。

以降、私は端的なコードを目指すことになる。 本質がなにかを見極めることができればコードは劇的に短くなる。もちろん、場合によっては泥臭くても今思いつく方法で解決したほうが良い場合もある。 だが、それだけでは成長がない。とにかくタンテ的なコードを目指す。それも、できるだけ短時間で、だ。 私の向上が、求道が始まった。

この求道はなにも「事前生成戦略」というテクニックにとどまらず万事に及んだ。 途方もなく困難に思われたことも、今ならできる。


  1. 結果は現象であり、この「結果」を「売上」や「人気」で測ろうとすると真実にはたどり着けない。↩︎

Git/Mercurial/分散バージョン管理システムの基礎

Gitに関する話をするとき、「ん???」となることがまぁまぁある。

で、多くの場合よく考えれば「GitHubの概念に引きずられている」ものが多いように感じる。

今回は、Git、そしてMercurialを含めて分散バージョン管理システムに関する概念と用語を、簡潔・明瞭に説明したいと思う。 なお、Bazzrその他に関しては私は使ったことがないので、分散バージョン管理システムの説明といいながら、GitとMercurialだけで進めさせていただきたい。

概念に関するもの

リポジトリ

恐らく、用語としてはこれが最も難しい。

「リポジトリとは歴史である」などといったりするのだが、どうも各々の定義にぶれがある。

であるから、Gitの場合は、.gitディレクトリ、あるいは*.gitディレクトリ(ベアリポジトリ)のことを指していると思えば良い。 また、.gitがあるディレクトリは「ワーキングツリー」である。

Mercurialの場合は.hgディレクトリを指す。

これは単にファイルであるだけではなく、ファイルの変更などを管理するための情報をもち、実際に管理することができる。

ローカルリポジトリ

ローカルリポジトリは、ワーキングツリーから見て、そのワーキングツリーが所属するリポジトリ(つまりは、ワーキングツリー先頭の.gitあるいは.hg)を指す。

ローカルリポジトリという語が出てくるのはリモートリポジトリに対する対比である。 なぜならば、リポジトリの操作はローカルリポジトリ(ワーキングツリー)上で行うため、「手元側」を意味することになるからだ。

リモートリポジトリ

リモートリポジトリは、ワーキングツリー、あるいはリポジトリから見て、自身ではないリポジトリを指す。

リモートリポジトリは一般的にはローカルリポジトリに対して何らかの関係性を持つ。ただし、持たない場合もある。 何らかの関係性とは、ローカルリポジトリがリモートリポジトリのことを登録しているか、リモートリポジトリがローカルリポジトリのことを登録しているかを指す。

「リモート」といっても、あくまでも「このリポジトリの外」の意味であり、ネットワーク越しであることを意味するわけではない。 むしろ、最も基本的なGitやMercurialの運用においてはリモートリポジトリはファイルシステム上にあるほうが普通であり、ネットワークにおけるリモートを指してはいない。

また、場合によってはそもそもローカルリポジトリ上で登録されたリポジトリのことだけを指してリモートリポジトリと呼ぶ場合もある。

コミット

リポジトリによって管理されるファイルのある状態の記録である。 GitやMercurialの変更は連続的に記録されるわけではなく、コミットした瞬間ごとが記録される。

コミットは本質的にリポジトリへの書き込みである。 このことから、最終的な修正が反映される権威リポジトリが存在する場合、その権威リポジトリに対するpush、あるいは権威リポジトリ上でのコミットを「コミット」と呼ぶ場合がある。 この場合、「そのリポジトリを更新する行為」を指すのであり、その行為をしうる者を「コミッタ」と呼ぶ。

ステージング

Mercurialにはない、「コミット候補」。

基本的にはGitの場合、ステージされたものはステージされた状態で保たれ、コミットされる。 ステージされてからコミットされるまでに変更は加えられないので、「コミットする前に考える」段階があると考えて良い。

実際のところ、ほとんどの場合ステージングは省略されている。

HEADはGit独自の概念である。

HEADは コミットを指している訳ではない 。 HEADはあくまで位置である。

リポジトリがリモートリポジトリと同期される関係にある場合、リモートリポジトリと同期した位置というものが記録されている。 HEADは全体で一番最後にコミットされた位置である。

コミットを取り込む場合、取り込む側(つまり、それによって変更される側)のほうがHEADの位置が前にあってはいけない。

フォーク

フォークは分散バージョン管理システムにおける用語ではない。

フォークは(由来は置いておくとして)ソフトウェアを複製し、複製元とは異なる未来を歩むことを意味する。

分散バージョン管理システムにおいては、行為としてはcloneすることがまさにforkすることを指す。 ただし、cloneした後に異なる未来を歩み、それを元のリポジトリに反映する場合や、恒常的に元のリポジトリの変更を反映する場合はforkしたとは言えない。

先割れしたフォークの先端が交わることはない。forkは決別なのである。

ブランチ

ブランチの概念はソフトウェアによって随分違う。

Gitの場合はあくまで歴史の分岐である。 ブランチを作ることでブランチ作成の起点になるコミットから、他のブランチに影響されることなくコミットを作っていける。

Mercurialの場合は、ブランチは位置情報になっている。 枝分かれしているというよりは、同じように時間が流れる平行世界みたいな状態である。

両者の大きな違いとして、Gitはブランチを作ったらそのまま完全に違う未来を歩んでもいいので、最初のブランチであるmasterブランチにそこまで特別な意味がない。 対してMercurialの場合は一種のコミットのような扱いになり、ブランチは最終的には取り込まれるか、クローズして捨てられるかすることを想定している。 だから、Mercurialの場合はdefaultブランチが本命である。

なお、Gitのmasterブランチは基本的に進んだHEADを持っているので、masterブランチをリリースブランチにするのはちょっとまずい。 リリースブランチは別に切るべきだ。 対して、Mercurialは一番進んだコミットを持つdefaultに合流するようになっており、あんまりリリースのことは考えてない感じになっている。

また、ブランチの大きな違いとして、Gitはブランチは削除できるが、Mercurialは閉鎖できるだけで削除はできない。 どうしても削除したい場合は方法がなくもないが、それはそれでMercurialでは本来禁止されている歴史操作を使ってそのブランチの世界線にあるコミットを全て消滅させるというすごいことをすることになる。

さらにもうひとつ大きな違いとして、Gitの場合ブランチは個々のリポジトリに属している。明示して送りつけない限りはpushあるいはpullするのはブランチ単位である。 対してMercurialは全てのブランチが共有される。だから、Mercurialでのpushあるいはpullするのはリポジトリ全体である。

競合 (conflict)

バージョン管理システムにおいて最も重要なのは、「同じファイルを同時に変更することに対して保護する」である。 古代のバージョン管理システムであるRCSでは、「変更可能な状態で持ち出せるのは1ユーザーだけ」という方法で管理していた。

GitもMercurialも、基本的には同一ファイルに対する変更を競合とみなす。

ただし、Gitの場合は変更点が重複していなければ競合にはならない。 Mercurialの場合は変更点が重複していなくても同一ファイルに対して変更していれば競合になる。

ただし、Mercurialの競合はそもそも歴史が割り込まれた時点で発生するため、こっちも向こうもそれぞれにコミットしたんだよね、という状態になったら確実に競合が発生する。 この変更が統合可能なのであれば、mergingという扱いにはなるものの、実際にはmergeは必要なく、単に「歴史を統合したコミット」を作れば良いようになっている。

ここらへんはGitのほうがきっちりしていて、Mercurialの場合はそれぞれが無軌道に変更を加えているとえらいことになる。 Gitではそもそもpush可能なのはベアリポジトリだけなのに対し、Mercurialではベアリポジトリという概念がなく、リポジトリは須らくワーキングツリーを持っているという考え方になっている。 でも、複数人で作業するような場合はワーキングツリーに対する変更を加えない、つまり自分でコミットを作成しないリポジトリを作ってそこにpushするようにしておかないと混乱を招くことになる。

pull request

pull request (通称プルリク)は、GitでもMercurialでもなく、 GitHubの機能である。 ちなみに、GitLabでは “Merge Request”という名前で同種の機能がある。

リポジトリに対してpushするためには、当該リポジトリに対する書き込み権限が必要である。 読み取り権限があればcloneできるため、cloneされるリポジトリは所有者が異なる可能性があり、元のリポジトリに対する書き込み権限がないことも少なくはない。

もちろん、書き込み権限があるのであれば当該リポジトリに対してpushすれば良いのだが、ない場合は当該リポジトリの書き込み権限を持つ者にpullしてもらうことになる。 しかし、その場合「pullしてほしい」と伝えなくてはならない。これを、「pullして欲しいと伝えて、変更点を明確にして、ついでにボタン一発でpullできるようにしたもの」がPull Requestである。

これに関しては誤解が深く、GitHubでGitを触り始めた人がだいたい混乱している。

アクション

init

リポジトリを作成すること、だが、どちらかといえば「今いるこの場所をリポジトリにする」のほうが実態を指している。

ただし、Gitにおいてはgit init --bareがあるためそうとも限らない。 この場合はベアリポジトリを単純に作成する。

このアクションはローカルリポジトリが存在しない状態で行う。

clone

リモートリポジトリの複製を作成する。

このアクションはローカルリポジトリが存在しない状態で行う。

push

ローカルリポジトリのコミットをリモートリポジトリに書き込む。 リモートリポジトリの書き込み権限が必要である。

pull

リモートリポジトリのコミットをローカルリポジトリに書き込む。 リモートリポジトリの読み取り権限が必要である。

add

基本的にはワーキングツリー以下のファイルをリポジトリの管理下に加えるアクション。

Gitの場合はステージングの際にも使用する。

Mercurialの場合、ワーキングツリー以下で明に除外されていないのに管理外にファイルがあることは望ましい状態ではないと考えるため、addの手順はまぁまぁ省略される。 Gitでは省略はできない。

merge

Gitにおいては異なるブランチを取り込むこと。

Mercurialにおいては、割り込みの発生した歴史を一本にまとめたコミットを作ること。

reset / rollback

resetはGitにおけるアクションで、ステージされたファイル、あるいは最新のコミットを取り消す。

Mercurialでは最新のコミットを取り消すrollbackがあり、Mercurialではコミットの歴史を操作するアクションはこれが唯一。 ステージして慎重にコミットするGitと違い、Mercurialは一発でコミットをキメてしまうため、rollbackは結構よく使うし、実際に簡単に使えるようになっている。

revert / backout

revertはGitとMercurialで全く意味が違う。

Gitにおいてはコミットを取り消す。この場合、そのコミットにおいて行われた変更そのものを元に戻す。 Mercurialは歴史を変更することはできないので、あるコミットで行われた変更を元の状態に戻す変更を加えたというコミットを作成する。それ用にbackoutというアクションがある。

Mercurialのrevertはワーキングツリーのファイルをコミットの状態に戻すことを指す。 これはGitであればgit checkout <commit> <file>に相当する操作である。 Gitのcheckoutはこれとは全く異なる「ブランチの切り替え」という機能も兼ねており、少々わかりにくい。

Gitには他にも歴史操作に関するアクションがあり、特にrebaseはまさに歴史修正主義者のためのコマンドである。

あんまり知られていないが、Gitにはblameという大変便利な歴史チェックコマンドがあったりする。 そして、実はMercurialにも似た感じのことができるannotateというコマンドがあり、hg annotate --user --numberとやればblame相当になる。

「revertするぞ」と言われたら、「お前のコミットは問題があるからなかったことにする」という意味になる。 例え実際にはMercurialを使っている場合でも「backoutするぞ」じゃなく「revertするぞ」と言う場合が多い。

diff

何か(コミット, タグ, ブックマーク, e.t.c.)の間でファイルの変更を比較するアクションである。

実はGitのdiffはGitリポジトリ外でも使うことができる。

diff -uと同じだろ?何が嬉しいんだよ」と思うかもしれないが、実はGit diffはインラインで変更を表示することができるのだ。 これがすごく便利。

stash

ワーキングツリーに対する変更を保留にするGitのアクション。Mercurialには全く存在しない。 ほとんどの場合、「作業すべきブランチを間違えた」という場合に別のブランチに変更を持っていくために行う。

すごく便利である。 そもそも、ブランチに関する操作はMercurialよりGitのほうがずっとやりやすい。

縦書きウェブ

「悠のおはなしのおはなし」というサイトを始めた。

これは、私が作家として物語について、あるいは物語をつくることについて述べている新しいサイトである。

主たる話題が美少女ゲーム(=アダルトゲーム=エロゲー)であるため、苦手な人も多かろうし決して閲覧を推奨するものではないのだが(PVを欲しているわけでもないし)、このデザインに関してはかなりがんばったので、その話をChienomiでしたいと思う。

縦書き

縦書きプロパティ

CSS3に縦書き関係のプロパティがあることは知っていたのだが、実際試すと随分印象が違った。

縦書き、あるいは段組というのは昔からあった。

段組のほうが古く、段組用のタグはHTML4で廃止されてしまった。 「見た目に関わるタグである」ということが理由だったが、その時点でCSSで代替する方法がなく、そもそも段組というのがHTMLにふさわしくないという判断が働いたようであった。 実際、誰も使っていなかったし。

また、単純に縦書きにすると非常にスクロールが長くなること、そして基点が左上であるため一旦全部右にもっていかなければならならいことからあまり快適性もなかった。

しかし、CSS3の縦書き+段組であれば、「画面サイズにちょうどよいように段組してくれる」という使いやすさだった。 画面に3段収まるのであれば3段、収まらなければ2段といった感じだ。 これは段の高さだけで、段数自体は無限になる。

もちろん、段組などしなければ縦書きは横にスクロールする形になる。 これは単純に縦書きか横書きかの違いになるため、考え方としては横書きとまるで変わらない。 しかし、PCの場合マウススクロールの方向がYだけ、タッチパッドでもX方向スクロールは無効であることが多いので、X方向にスクロールするUIというのは大変嫌われる。

そのため、「縦書きの無限段組」というのは非常に良い挙動だと思う。

ただ、「ePubみたいにフリックでめくりたい」という要望が出たので、後述するページめくりを入れた。

不安定な挙動

理屈上では理想的なのだが、現実としてはやはり「縦書き段組」なんてものをけんしょぅする人が少ないからか、なかなかbuggyである。

なんといっても、ボックスのサイズ計算、位置計算が全ておかしくなる。そして、段組の計算もおかしい。

さらに、リサイズ時の段数が読めない。同じサイズにリサイズされても条件によって結果が変わる。

結局、その抑制のため、幅を取るエレメントを横に並べず、HTML全体を幅90%に制限するという方法をとった。

なお、FirefoxよりはChromiumのほうが安定して描画してくれる。

レスポンシブルと4kスケーラブル

4kの大きいディスプレイを買って感じるのが、「大きな画面を想定していないサイトが多い、ということだ。」 幅が極端にあると、コンテンツは真ん中にちょこんとあるだけ、という状況になりやすい。

トヨタのウェブサイトは現在は真ん中コンテンツ型で、TGRに関しては写真だけ幅いっぱい、という仕様になっている。

幅が小さいモバイル系デバイスの設定はしても、幅が大きいデバイスに対する対応というのはちゃんとしていない、という感じだ。

そこでこれに対する対応として、私は珍しい指定を入れた。

フォントサイズが画面幅によって決まる、ということである。 このため、大きい画面であればピクセル数によらず文字は大きくなる。 段組の文字数を決める上でも扱いやすい。

この考え方、今の「振れ幅の大きすぎるディスプレイサイズ」問題に対応できる気がする。

幅は原則画面いっぱいにしており、幅広ディスプレイにも対応する。 しかし、あまりにも行数が多くなるととても読みづらいため、34行までに制限している。 そのため、さすがに上下タイルとかされると真ん中に置かれてしまうのだが、大画面の最大化ではそうはならない。

ちなみに、今回行数をどうしようかと思って文庫本を色々数えてみたのだが、16から18行が多く、一部19行というものがあった。 さらに合わせたかったのだが、さすがに幅が少なく、見開き分ということで34行としている。もちろん、これはフォントによって結果が変わるが。

UI

ボックスレイアウトが縦書きすると狂うこと、本好きの感覚としては余計な表示は欲しくないことから、トグルボタンを2つ置く、という方法で対応することにした。 ちなみに、いつも通り、CSSのみの対応である。

操作できるものが他になければ操作してくれるものなので、割とイケると思っている。 微妙に表示としては邪魔なのだが、それはご愛敬。HTMLを90%にしたおかげで右側が少しあきやすく、この問題は少し軽減されている。

ハンバーガーメニューはともかく、純粋なトグルというのはあまり見ない設計だが、simplifyという観点から言えば悪くないと思う。

ハンバーガーメニューですらないUIに気づくかどうかについては… トップページにでも書いておいてなんとかしよう。

ページめくり

前述の通りePubのようにページをめくりたいという要望に応じて、swipeとflick両方に対応する簡単なスクリプトを書いてみた。 jQuery Mobileどころか、jQuery自体使っていない、PureJSによるものである。

考え方は単純で、タッチ開始時に座標を保持し、タッチ終了時の座標と比較する。 Y軸は普通にスクロールだから、問題にするのはX軸だけ。 差が閾値以上にあればスワイプ、またはフリックしたものとみなす。

今回、閾値は「画面の1/3以上」とした。意外としっかりとやらないとめくれない。 感触としていまいちなので、今後もっと小さい操作でもめくれるように調整するだろう。

話をすごく単純化しているが、意外とこれで問題なく動作する。

1段の高さははっきりしない上に取得もできないので、単純に画面半分をスクロールすることにした。 おまけのようなものなので実用性は状況によってはあまりないが、ないよりはよかろうということでアクセシビリティツールである。

デザイン

やや黄色がかった背景とグレーの文字は目に優しく、「本っぽい感触」を求めてみた。 本であればもっと黒いのだが、印刷物よりも画面のほうがはるかにコントラスト比が高いことから、よりグレーにした。

タイトルロゴは珍しく実用的にfloatが使われている。 最近はfloatは思い込みで横に並べるのに使われがちなので、多分珍しい。 ロゴの配色は「白枠、色付き背景」というライトノベルの背表紙のスタイルに合わせてみた。

左上に入るページタイトルは、小説の上部に章タイトルが入るスタイルを意識している。

「本なんて特に有益ではない」「本で勉強する必要はない」「本がありがたく高尚なわけではない」という発言を繰り返しているので誤解されがちなのだけど、私はそもそも本好きで、めちゃくちゃ読むほうだ。 最近は「本が入り切らなくなったので動画を見るようになり、動画に飽きたのでエロゲーをやるようになった」という感じで、割と物語ジャンキーみたいな感じで何かしら物語に触れていたいタイプだったりする。

本屋にいけば際限なくに時間を消費してしまうからあまりいかないように心がけているし、横浜に図書館がないことは大変嘆いてもいる。

電子書籍も(普通にPDFなどプレーンな形式で配布してくれるならば)好きだし、紙の本も好きだ。 ただ、どちらかというと私の場合、紙の本なら本棚に入れておけば何の気なしに読んだりするが、電子書籍は明確な動機がないと読まないので、それを理由にどちらかといえば紙の本が好きで、家に無限の広さがあり、引っ越しの手間も考えなくてよいのなら書庫を作って本いっぱい買いたいくらいである。

本を読むタイプの人間としては、「本っぽさ」というのは長文を読むときには結構欲しい。 流し読み、というか斜め読みするときは横書きが圧倒的に早いけど、物語への没入なら縦書きがいい。

紙っぽさ。縦書き。本っぽい文字数。本明朝。 これは私のこだわりであり、これをきっかけに「物語を読む」ことが好きな人が増えてくれたらいいなぁ、という気持ちも込められている。

また、このデザインは、webページのデザインとして上がってきたものを見たときに、「ウェブはかくあらん」みたいな感覚をぶち壊したい、というのもあった。 そもそもそのサイトでは「従来にないデザインの方向性を目指したい」というのがあったのだけど、なぜか「デザイナーから上がってくるwebデザイン」ってすごくありきたりというか、みんな同じようなものを出してくる。(ちなみに、違う感じのを出してくる人は実用性の欠片もないとんでもないものを出してくることが多い。)

それが常識としてこびりついているのなら、「ウェブはこんなことしたっていいんだよ」というのを声を大にして言いたかった。 だから、常識やお決まりからは思い切りかけ離れたサイトを作りたかったのもあります。それは、ウェブのクリエイティビティを失ったエンジニアに対しても。

縦書きプロポーショナル

今回、vpalを有効にしており、縦書きでプロポーショナルメトリクスが有効になっている。

徹底して「文庫本っぽく」仕上げているにも関わらず、書籍ではまず見ない縦書きプロポーショナルは、私のちょっとしたチャレンジだ。 これが見やすいかどうか、ぜひ意見を募りたいと思う。

ERINA the emotional AIのコンポーネントモデル

論文前に公開するERINAの解説・紹介、第二段である。 今回はERINAがどのようなコンポーネント設計から成り立っているかを紹介する。

ERINAは他のAIと比べ、かなり複雑で大規模なプログラムとなっている。 多角的なエミュレーションのために処理は泥臭く複雑だ。 さらに、非常に長期に渡って開発されているために、開発言語がかなり多く、開発時期もバラバラのコンポーネントがごった煮のようになっている。 そもそもERINAの目標は果てしなく遠いものであり、その開発の事前予測は事実上不可能である。このようなプログラムは非常に高い可能性で混沌の中で終焉を迎える。

だが、実際はERINAはそのような失敗に至るプロダクトのような特徴を持ちながら、うまく調和がとれ、良い結果をもたらしている。 古いコンポーネントの改修は不可能に近いほど困難だが、新規開発や古いコンポーネントの置換えはそれほど難しくない。

これは全体を通じる設計の効果によるところが非常に大きい。 個々のコンポーネントは追加することも削除することもできるし、設計に基づく約束事さえ守れば全体の設計は個々のコンポーネントの設計には立ち入らない。 これはいわゆるカプセル化やダックタイピングに通じる考え方で疎結合しているのである。

このような設計には私はふたつの物事からインスピレーションを得た。 ひとつはUnixのツールボックスであり、もうひとつは生物的モデルである。 生物においてそれぞれの要素が適切に自分の役割だけをこなすことにより、結果的に全体が調和して生命を形成する。だが、例え一部の要素が機能しなかったとしてもそれが直ちに生命を損失することにはつながらない。ERINAのような予測も把握もできない、変化しつづけるものにはこのような設計が適しているように思えたのだ。

なお、ERINA/Surtrのコンポーネントの命名だが、北欧神話に関するものと脳神経に関するものを使っている。 だが、北欧神話の用語の使い方としてはやや間違っているし、脳神経に関する用語に関しては多分大いに間違っている。 論文を書く時に問題になりそうでちょっと困っているのだが、そういうふうに名前をつけてしまったてので大目に見ていただければと思う。

さらに内部的に使っている用語としては

  • ヴァルキリー = データを収集、分析、登録するワーカーのこと (実際はプログラム自体よりワーカーを指していうが、プログラム名称にも一応入っている)
  • エインヘリアル = 収集対象になっているデータのこと
  • ヴァルハラ = エインヘリアルを解析したデータベース(somaデータベース)のこと
  • アスガルド = データベースやERINAインスタンスなど、「こっち側」
  • ミッドガルド = ERINAがコミュニケーションを取る相手や、取得可能 or 未取得な情報などの「あっち側」

という扱いである。ゲームとかファンタジー小説とかが好きな人ならなんとなく伝わるかもしれないが、そうでないと全くわからないだろう。 これについては、「短い言葉で端的に概念を表すことができるようになったことは素晴らしいが、よく綴り間違えて痛い目を見る」という感じである。 脳神経に関する言葉を採用したのはその問題を解消するためだったが、結果「概念が若干似ているがために用法として間違っているように感じられるものになってしまい、口外するのが恥ずかしい」という別の問題を発生してしまった。

orbital design

orbital designは処理対象をキューとして生成し、複数のワーカーが順に処理を行っていく独自のモデルデザイン。

ワーカーは基本的にはジョブスケジューラによって生成される。 この起動時にキューも生成される。 このことから場合によっては生成されたワーカーがキューを消化する前に次のキューとワーカーが生成されることになるが、構わず複数サイクルが同時に回るようになっている。

つまり、処理対象はキューが生成されるときに確定されている。 この瞬間から状態が変更されたとしても影響を受けない。 これを実現する手っ取り早い方法は「スナップショットを取って、スナップショット上のデータを対象にする」である。

orbital designによりコンポーネント全体で連続的処理を行う構造になっている。 完成、あるいは完了という概念がなく、それぞれのコンポーネントが自身が担う処理を最新の状態にアップデートしつづける。

これはアジャイル開発やPCDAサイクル(これはちょっと違うか?)と似たような考え方でもある。

ERINAは実装面では決して良いものではないのだが、それでもなんとか動作しているのはこれを含めたデザイン面が大きい。 それぞれのプログラムが独立してサイクルを回せるようになっているため、技術レベルや実装言語などによらずシンプルな約束事だけでコンポーネントを作り、使っていくことができる。 ずっとむかしに作られたPerl製コンポーネントが今も動作するのはこれが理由で、ERINAの実装言語がやたらに多い理由でもある。

基本的にデータの受け渡しが必要なときは標準入出力経由のテキストであると決まっている。 最近新しく登場した部分はYAMLになっているが、XMLだった時期もあるし、もっと独自の形式だったときもある。 ただ、基本的にコンポーネント種別ごとには受け渡しフォーマットも統一されている。 実際には変更されることもあるのだが、そのような場合は一世代ごとに形式を変換するフィルタがあり、これを世代数分経由することで統一を図っているものもある。

orbital designとこの規約どちらが先かというのは難しいところだが、決めたのは標準入出力経由というのが最初で、 コンポーネントを連動させると大変というのはかなり初期からあった問題なのでほとんど最初から独立してそれぞれが自分のことに専念できるように書かれていた。 その発展として最近採用されたのがorbital designというわけであり、初期は繰り返し直列的に処理していくシェルスクリプトだった。 明確にorbital designというコンセプトを決めたのは2015年である。ただ、それらしきことはそれ以前からしていた。

orbital designのメリットを簡単にいうと、AとBというふたつのデータベースがあり、それぞれが解析によって得られた結果を書き込み、その解析はAのためにB、BのためにAが使われるとき、AとB両方のデータベースを連続的に同時に更新したとしても時間と共に精度が向上する、ということにある。 ERINAの場合は話はそこまで単純ではないが、相互に必要とされるデータベースの更新を並列かつ連続的に行うためのデザインとしいうことには変わりない。

orbital designは結構幅広くメリットがあり、例えば計算することそのものは独立しているため入出力の問題さえなんとかなれば非常にスケールしやすく、コア数やマシン数で稼ぎやすい。 特にERINAは通しで処理した場合データ量増加に対して処理量は指数的増加するようなモデルであるため、非同期かつ連続的に更新していけることは必須である。

このデザインは設計・採用するのが容易で、見通しがよくなり、実装も楽で、停止もしやすい、といったメリットがあるので結構有用なのではないかと考えている。

入力

Ninja Valkyrie

Ninja Valkyrieはデータの収集を担う。

プログラム的にはそれぞれのメディアに合わせたものが存在する。 その詳細は秘密であるが、 インターネット(特にウェブ)のみからデータを収集しているわけではない という点は強調しておこう。

基本的にはERINA(というかSurtr)にとっては情報のことをエインヘリアルとして扱うことになっている。 私は北欧神話にそこまで精通しているわけではないので、斥候を担う者にちゃんと名前があったりするのかもしれないが、それは知らないためNinja Valkyrieと名付けられている。 もちろん、斥候だからNinjaだ。

Ninja Valkyrieはあくまでデータをデータとして取ってくるところまでを担う。

Guardian Valkyrie

Guardian Valkyrieはデータのデータベースへの登録を担う。 大部分がファイルシステムベースのデータベースになっているため、Guardian Valkyrieの出番は非常に少ない。

最も大きいのは動画専用のフラグメントオブジェクトファイルシステム(Surtrコンポーネントの一部として専用に開発されているもの)に動画を切り刻んで登録することである。 この処理に関してはそれ以上解析する余地がないため、Knight Valkyrieによる処理が行われない。

ただし、この方法はあまりうまく言っているとはいえない。 動画の解析の難しさもさることながら、やはり本質的にデータが失われることが重大すぎる。 もちろん、この方法は「収集する動画データを動画として保持しておくだけのディスクスペースがない」ことに由来するのであり、あまり実用的な期待は持てない状態だ。

Knight Valkyrieと両立されている関係にあるGurdian Valkyrie最大の仕事はファイルインデックス(取得日時や更新日時の情報を持ったデータベース)の生成であり、これはKnight Valkyrieがキューを生成するために必要になる。

Guardian Valkyrieは基本的にはNinja Valkyrieから呼ばれる。 この中には現在の形式に合わないエインヘリアルを連れてくるNinja Valkyrieを現在のフォーマットに合わせる処理を含んでいる。 このためにGuardian Valkyrieの呼び出しコマンドはラッパーとなっており、呼び出し形式に合わせて別のGuardian Valkyrieに移譲する方式。 もし現在望まれる形でデータを保存するNinja Valkyrieから呼び出された場合はGuardian Valkyrieはなにもしない。

Knight Valkyrie

エインヘリアルたるデータをヴァルハラたるデータベースに登録するのがKnight Valkyrieである。

このデータベースに登録されるのはインテリジェンスが直接扱うことのできる細分化した情報の断片である(somaと呼んでいる)。 Guardian Valkyrieが取得したデータ全体をデータベースに登録するのに対し、Knight Valkyrieはデータの解釈や分解などを行う。

Knight Valkyrieもひとつのデータフォーマットに対して一種類だけしか存在しないわけではない。 特に会話文テキストを解釈するKnight Valkyrieに至っては30種類以上に及ぶプログラムが存在する (そしてそれはそれぞれ異なる言語で書かれていたりもする)。

ひとつのデータに対してそれぞれのvalkyrieがそれぞれの解釈によってデータを登録することはERINAのデザインにとって重要なものになっている。 これは特定の考え方や特定の視点、特に常識や前提にとらわれることなく発見を続けていくためには例え思い違いで役に立たないようなものであってもそれぞれのルールで解釈・解析することが必要なのだ。

Knight Valkyrieはさらに分解した情報を元に「盤面」を作るという処理も行う。 実際にsomaとして扱われるのはこちらのほうで、分解した内容は盤面に対してタグ付けしたような状態になる。 分解した内容は検索に使われるほか、次段のシナプス形成においても必要となる。

これがKnight Valkyrie単体で担っていることに違和感があるかもしれないが、実際はこのあたりを分けて話すのは難しい。 なぜならば、盤面を構成するのは「分解した要素の構成」だからであり、要素の集合は盤面のIDとして機能する。 おおよそこのデータベースはキーバリューストアのようになっているのだが、要素の集合はキーとしても値としても動作する。

そして盤面側は複数のデータベースがレイヤー状になっており、このほかに要素に基づく統計的データベースが複数存在する。

Knight Valkyrieの動作は最も実装が難しいもので、「思考とはなにか」「情報は何を持っているか」ということを決定づけることになる。

Connexon

Connexonは盤面(soma)に対して接続可能な盤面を登録する。 ちなみに、接続可能な盤面を表すキー名はsynapseになっている。

この処理自体は単純なものであり、入力過程にあるものでは唯一稼働している実装が1種類しかない。 ただし処理が単純だからといって実装が簡単なわけではない。Connexonが簡単なのは、「どのような要素に分解し、この要素はどのような意味を持ちうるか」という設計をKnight Valkyrieの時点で行う必要があるために、Connexonによって新規に設計する必要がないということに過ぎない。

また、処理は単純だが、接続可能なsomaを探索するために全somaをあたる必要があり、しかもsynapseが更新されると接続可能なsomaも更新されることになるため非常に長いループを回すタイプである。ERINAコンポーネントの中で最も長いCPU時間を求めるのがConnexonである。

そして処理量の関係上、現状Connexonは全てのsomaを巡回しない。Connexonのキュージェネレータが「Connexonが当該somaをいつ巡回したか」「somaがいつどれくらい探索されたか」という情報を元にキューを生成する。だからsynapseが長く更新されていないsoma、及び頻繁に参照されているsomaが優先順位高くキューに入ることになり、生成後一定時間経つとキューの状態に関係なくConnexonキューは終端を返す。

なお、私は解剖学・生物学の知識もそんなにないので、名前に対するツッコミはお控えいただきたい。 ただし、新しいネーミングのご提案はいつでも大歓迎である。

Neuron

NeuronはKnight Valkyrie及びConnexonとは別のフローでエインヘリアルを接待する。

これは主に語彙や知識に関する情報収集と接続を行うものであり、一般的なビッグデータ処理に近いものになっている。 Neuronによって処理されるデータは盤面を作られることはないのだが、そもそもKnight Valkyrieがこのデータベースを使う。

Neuronが作るデータベースは解析だけでなく生成側でも必要となる。

対話

Erina receptor

Erina receptorは入力された情報から盤面の探索を行うものである。

Erina receptorはまずKnight Valkyrieを使って受け取った要素を解析する。 そして、「与えられた要素」と「コンテキスト上キープしている要素」から得られる要素を、Erina context finderによって絞り込み、これによって残る要素を元にコンテキスト上のcurrent盤面のsynapseを探索する。

Erina receptorは探索されたsomaのIDとスコアを返す。 なお、somaは「発言」ではなく「状況における言動の要素」の盤面であるため、「無視する」といった行動もありえる。

Erina context finder

状況を推測するための調整用フィルタであり、アルゴリズムやデータベースを含めた完全手入力である。

これは、「前回の会話からこのメディアでこれくらいの時間が空いたら前回のコンテキストとの関連性を下げる」というような処理を行う。

これが調整用であるのは、そもそもKnight Valkyrie側に「応答時間の変動」のような要素を理解する処理が入っているからだ。 だから学習的にしきれない部分をフォローするためにあり、基本的にはこのプロセスのみが担っているものはないはずである。

だが、とても重要で、このフィルタがないといまいち自然な応答にならない。 「その話今する!?」みたいな不自然な感じが残ってしまうのだ。

Erina emotion engine (effector)

Erina emotion engineはかなり複雑な処理をするため複数コンポーネントに分かれているが、それらは直列に処理するためここでまとめて紹介する。 なお、Erina emotion engineはERINA固有のものであり、Surtrにはない。ERINAが単独で存在する意味は元はSurtr emotion engineと呼ばれていたこのコンポーネント群にある。

Erina emotion engineはneuronデータベースをかなり細かく読み込む。 これはキャラクターづくりにも関係している。

単純にErina receptorの結果から「それらしい振舞い」を導き出すことはできる。 だが、実際にはErinaは常に最善手を指すわけではない。 もし常にreceptorが最も高いスコアをつけたものを選ぶと「振舞いに全くゆらぎがない」「人物に一貫性がない」といった問題が出る。 これは非常に重要なポイントで、このような特徴に対しては人間はかなり敏感に反応し、不自然さを感じてしまう。

そのため、Erina emotion engineはまず「キャラクター性」というものを持つ。 これを実現するためにErinaインスタンスは自身に特化したキャラクターデータベースを使用する。 ERINAにとっての「学習」はこのキャラクターデータベースの更新にあると言っていい。

Erina emotion engineはまずキャラクターデータベースに基づいてreceptorが返したスコアを書き換える。 この機能のためにreceptorは複雑なスコアのつけ方をしている。

この処理にはコンテキストも使われる。 生活状態や感情状態という要素があり、これはシミュレーターのように計算・動作する。この部分はおおよそ(硬派な)シミュレーションゲームのような処理である。 このコンテキスト処理が「ゆらぎ」を担う。この処理のときに行動条件も出す。これは「返信をわざと遅らせる」といった処理のために使われる。

コンテキストの生成・更新もErina emotion engineの機能の一部であり、この情報はインスタンスデータベースに書く。

キャラクター性による再評価と出力の編成は近いものがある。 キャラクター性を評価した段階で意味的にはこのような振舞いをする、ということがわかっている。 「どのような心理状態か」「どのような行動か」ということは新たに定義しなくても要素の組み合わせで確定できる。 なにを発言するかという点で主旨はなにか、というのはsoma側にある。そのため「応答すべき内容が表現段階で意味的に間違う」ということはまずない。

その状況における意味的な言葉というのはKnight Valkyrieが集めている。 これは主には同じ要素構成になる言葉(綺麗な文章であるならば「文」)の共通要素を抜き出す方式である。

そしてneuronデータベースには発言の編成がある。 そもそもKnight ValkyrieもNeuronも共に「誰が誰に対して発言したか」という情報を取り扱うため、模倣するのは比較的簡単。 特定の組み合わせから見た一連の発言だけでは非常に表現に乏しくなってしまうため、なるべく類似の発言傾向をまとめるようになっている。

例え意味や心理を完全に模倣できていなくても、ある一定の傾向を持つ発言を元に、意味的にどのような発言をすればいいかということを外さないように組み上げるため「ちゃんと意味が通っていて会話が成り立つ」感覚が補助される。 実のところそもそもERINAは人間の身勝手な補完と錯覚を突くように作られているため、発言を精査すると意外とあやふやである。

別の角度から見ると、他が分析・理解・解明を追求しているのに対し、この出力部分だけ結果が出ることを重視した全く異なるアプローチになっている。 そうなっている理由は、「この部分だけがERINA固有のコンポーネントだから」である。データベースの利用方法として検索や検証といった機能はSurtr側にあり、ERINAは基本的にコミュニケーションの様態を模倣する方向であり、Surtrのコンポーネントとはアプローチが異なる。 現在の試行の範囲では理解など忠実な再現において自然なコミュニケーションを実現することはかなり遠そうだが、ERINAがそれを目指すことはない。 なぜならばERINAの意図、そして命名理由からも外れてしまうものであり、もしそれを目指すとしたらERINAとは別のAIとして開発することになるだろう。

アルゴリズムに踏み込んだ話はおいておくとして(なんとなく筆がそっちに走ってしまったが)、Erina emotion engineの手順としては

  1. キャラクターとコンテキストをロード
  2. コンテキストの解析と更新
  3. receptorの応答の受け取り
  4. キャラクターに基づいて結果を編集
  5. 応答に使用するsomaを決定
  6. somaとキャラクターに基づいてneuronデータベースを検索し応答語句を編成
  7. コンテキストを更新
  8. 結果を出力

となる。

コンテキストの更新をreceptorの応答を受け取るより前にも行っているのは、receptorが探索にコンテキストを使うため。

Erina controller

receptor, emotion engine, media frontendとのやりとりを仲介するコンポーネント。 一応、これがERINAのインスタンスプロセスになる。

なんのデータベースをロードするかはこのcontroller起動時点で決定する。 というよりも、インスタンス生成時に決定する必要があり、変更は難しい。変更してしまうと初期化されてしまう。

コンポーネントとのやりとり以外ではコンテキストデータベースとキャラクターデータベースの初期化処理を受け持っている。

Erina media frontend

入出力処理用レイヤー。

単に読み書きするための抽象化をしているのではなく、文字数、送信ペースなどの「自然なやりとりのスタイル」を定義するデータベースでもある。 ちなみに、これも学習ではなく手入力。

emotion engineが語句編成のときに使うものはこのレイヤーの種類分あるわけではなく、メディアタイプはごく少ない種類だけで、あとはちょっとした調整パラメータがあるだけ。 調整パラメータは「連続送信」「顔文字」「絵文字」「Unicode」「句読点」などなど。

例えば連続送信型とした場合、改行したりするよりも複数の発言に分けるようになり、文の構成が簡素になる。 あまり推敲していないような文章になりやすく、端的でかつ文単独では意味が伝わらないような発言を選択する。 連続送信型でない場合は、発言主旨を一度の発言にすべて含めるようになる。

Mimir Yokohamaで続く改修、力を注ぐ

2018年8月5日、Mimir Yokohamaのソースリポジトリにはじめてのタグ、5.0が打たれた。

なぜはじめてのタグなのに5.0だったのか。

これは、Aki SI&Eのプロトタイプを1.0, Aki SIEのウェブサイトを2.0, Mimri YokohamaのWordPressを3.0, 現在のウェブサイトになったときを4.0とカウントしたものだ。

つまり、はじまって以来の、同一システムのままのバージョンアップとなった。

しかもこのタイミングである。 先日、大幅な編成変更と、いいね機能、コメント機能の追加を行った。 これで新しい船出だというタグではない。かといって同バージョン最終仕様としてのタグでもない。

これから次々とアップデートが予定されている。 その中での一区切りだった。

13年積み上げてきたものとは

PureBuiler Simplyの原型となっているのは2005年のACCS1である。

もうブログが流行り始めていた頃だったが、既に「事前生成戦略」に関するイメージはあった。 ブログを避けた理由は、一連の流れを持つ記事群を拾いにくくなること、そして時間とともに消えてしまうことだった。

「普遍的な内容を、きちんと分類して読みやすいように提供したい」と考えたわけだ。

様々な機能、様々なアイディアがあった。 実装されたものもあるし、実装されなかったものもある。 言語もPerl, PHP, Zsh, Rubyと変わってきた。 このような変遷をたどっているのはEQAIとこれだけで、まさに私のライフワークであり、また成長の軌跡でもあった。

この中でこだわってきたものもある。

例えば、デザイン性を保ったまま軽量・高速なウェブサイトを構築すること、可能な限り高いアクセシビリティを提供すること(環境や回線の違い、身体的ハンディキャップなどで差別しないこと)などもそうだ。 意味あるコンテンツを、読みやすい形で提供する、というのもある。

ACCSが追い求めてきた機能は次のようなものだった。

  • カテゴリで分類されて探しやすいインデックス
  • 検索機能
  • 意思表示機能 (コメント、と考えていることが多かった)
  • 連続した記事のページめくり
  • 要約の先読み
  • インライン用語集
  • prev, next, glossary, index, description情報を持たせる

PureBuilder Simplyは当初、Mimir YokohamaのWordPressページで提供されている全機能を提供する、ということを目標としていた。 これについては既に達成されている。そのために、従来のPureBuilderにはなかったタグ機能なども追加された。

「いいね機能」「コメント機能」はWordPressに完全な意味で標準であるものではないのだが、事実上付属するようなもの(特にコメント機能は)なので、これを追加したことで、事実上「WordPressを置き換える」というミッションは完遂した。

だが、同時に既にWordPressにはない機能の搭載もはじまっていた。 用語集機能はWordPressではなく、PureBuilderの伝統に由来する。そう、ACCSが目指していたものを達成する、という次のミッションに向かいはじめたのだ。

そこでつけられたのが5.0タグだった。

用語集機能

用語集機能という発想のスタート地点は、私が使っていたWindows 98SEマシンに搭載されていたインライン翻訳ソフトだった。

カーソルを載せるだけでその単語を翻訳してくれる、というソフトウェアは今持ってそれ以上のユーザービリティを提供するものはない。

「カーソルを載せたらわからない言葉を教えてくれる」というのは最高に便利だと思ったのだ。 現在はニコニコやはてなが似たような機能を提供しているが、あれはページが変遷してしまうため私からすれば理想的ではない。 どらちかといえばWikiに搭載されているインライン展開のほうがずっと理想的だ。

この機能は

  • 表示後にJavaScriptでtreatする
  • 生成時にHTMLを置き換える形で組み込む
  • ソースドキュメントを改変してから生成する

という3つのパターンを行ったり来たりしている。

PureBuilder SimplyではPost Plugins/Pre Pluginsの構造からこの3つとも選択肢として取ることができる。 Mimir YokohamaではPost PluginによるHTML置換え方式を取っている。

このあたりは試行錯誤の成果といえるだろう。 用語集ページも含めてYAMLの辞書ファイルから生成しており、文書に対して特別な処理は必要ない。

次の記事、前の記事

ACCSで最も苦戦したのがこの機能だ。

この解決については何度か言及しているが、今は前後関係をメタ情報として書く、という仕様になっている。

解決方法としては後退しているように見えるが、前後関係の自動解決はファイル名なり、もしくはなんらかのヒントなりを厳密に管理する必要があり、結構バグりやすいということを経験したのだ。

複雑な置換えやユーザーの管理によってバグを発生しうるようなものであるならばメタ情報をユーザー自身が書くべきだ、という割り切りは、これまでの歴史の中で手にしたバランス感覚である。

実のところ今の構成では前後を自動化することは難しくない。 だが、ユーザーがそれを守ってくれることを期待すべきではないだろう。

Post pluginsはページ生成が終わってから実行されるが、これは「他の記事の情報を取得できるようにする必要がある」からであり、 環境変数$pbsimply_indexesという形でデータベースのパスが渡っていることから

のようにして取得できるし、 連番に限るのであればもっと簡単に

とできる。 ちなみに、ディレクトリを認識させる方法は最新のコミットで環境変数$pbsimply_subdirに含まれるようになった。

このように「できるけれど、あえて手書き」だ。 これは、問題を簡単にするためと、この処理をPandocテンプレートを通じて行いたいためだ。 Pre Pluginsを使えばできるが、かなり複雑なことをすることになるため避けている。

意思表示機能

過去には「コメントを直接にHTMLファイル化し、objectで読ませる」ということをしていたこともある。

表示するかどうかを別として、コメント機能はそのときとあまり変わっていない。 表示させるために必要な部分を削ってシンプルになったくらいだ。

このような機能は本質的な部分は極めて簡単に書けることはこれまでの経験によって証明されている。 どの程度正当性を検証する必要があるかという点がwebアプリケーションの分量になる。

要約の先読み

まずは要約を入れる

いよいよ今回のハイライトだ。

descriptionへの対応自体は最初のリリース時点で

という記述があり、対応はちゃんとしていた。 だが、「書くのが面倒」「書いてもあまり意味がない」ということで放置していた。

「descriptionの先読み」は今まで実装計画には入っていたが、実装されたことはなかった。 そもそもdescriptionってSEOのために入れられているくらいで、「descriptionを読ませる」という発想はあまりない。

Firefoxだとこんなふうにブックマークのプロパティを表示するか、ブラウジングライブラリー上で詳細表示にするとdescriptionが表示されたりするのだが、これを見たことがある人という人は地球上に5桁いないのではないだろうか。

Firefoxのブックマークの詳細

だがdescriptionは入れたいと思っていたし、それを活用したいと思っていた。

そもそもの発端はトップページのレイアウト更新で、最新の更新記事と要約を(ニュースとは別に)表示したい、ということだった。 「どうせ記事の要約書くんだったらdescriptionに入れようよ」ということだ。

実はAtomフィードもスタンバイしている。

要約を見える形に

だが、これだけではおもしろくない。どうせ要約を表示するのならばぜひともユーザーに見える形にしたい。

私の文章は基本的に長いので(体系的でない短い文章に価値を感じていない)、読むのがしんどい人もいるだろう。 読むかどうか決めるために要約は重要だ。

要約を見たいタイミングとはいつだろう? やはり記事を読み始める前だろう。ならば本文前に

とかやってやればいいし、そのほうが効果的なのかもしれないが、既に「文書情報」という項目があることを考えるとちょっといただけない。

そこで文書情報に追加した上で「記事タイトルをクリックすると文書情報にジャンプする」という仕様にした。

これはヘルプページにも書いてあるけれども、誰も気づかなそうだ…

もうひとつ、利用者は多くなさそうだが、カテゴリインデックスがある。 ACCSとしてはこれが中心であり、ぜひとも使って欲しい機能だ。 世の中、情報を整頓するということに怠けすぎて、検索が全てになってしまっているので、使われていないような気もするけれど…

しかし私の意図としてはこのようなインデックスを活用してほしいというのがあるし、やはりタイトルだけではわかりにくい。 かといって変遷するとだるいので、変遷せずにインデックス上で要約を確認できると便利だ。

これはタイトルで関心をそそられた後の二次的な情報であり、通常は一覧性が高いほうがいい。 というわけで、ツールチップにしてみた。

PureBuilder Simply ACCS上でDescriptionを扱う

単純には記事タイトルにtitleで入れてあるため、ロールオーバーツールチップとして表示される。 だが、スマホだとこれが効かないので、補助的に“📖?”と表示して、これをタップすればツールチップが表示されるようにしてみた。 全く標準的でないインターフェイスなので、あまり気づいてもらえないような気もするけれど…

PBS ACCS用ツールチップ実装

何度見てもJavaScriptの複数代入が慣れない。 慣れればみやすそうだけども。

世の中的には割と珍しいDOM操作をしているが、これはelementに対してイベントリスナを設定するためで、innerHTMLだと二度手間になる。 基本的にやっていることは「記事部分 > リスト全体 > リンク」と絞り込んでいって要素を作成して追加する、という手順だ。 末っ子要素を追加するとき(今の要素の親要素の最後の子要素として追加する)はelement.parentNode.appendChildという手順は覚えておいてもいいかもしれない。

glosarryと共通のコードがライブラリとして読まれるようになっている。ライブラリはdeferだがasyncではない。

900pxを堺にしているのは、「サイドカラムがあるのであれば表示領域は少なくとも右側にサイドカラム幅はあるが、シングルカラムになるとそうではない」からだ。(このページのシングルカラム境界は800pxである)

機能チェックしているが、document.addEventListenerできないブラウザでJavaScriptに対応しているものはあまり残っていないだろうし、あっとしても単純にイベントリスナー設定時にエラーになるので放置してもいいかもしれない。 ただし、間違って複数回ライブラリが読まれたときのためにArt.tooltipはしておかないとイベントリスナが複数設定されてしまう。

PureBuilder Simplyはうまくいっている

PureBuilder Simplyがここまでうまくいっている理由としては、やはりPandocの強力さがなによりだろう。

PureBuilder SimplyはPandocが持っている機能をちょっと拡張する…という考え方をしている。 今までドキュメントジェネレーター自体を制作していた(PureDoc)ことと比べると問題はかなり簡単になっている。

Pandocの動作は必ずしも簡潔ではなく、自分で実装するのであればドキュメントジェネレーターにここまでの機能をもたせることはないだろう。 だが、Pandocがある以上はPandocを使いたい。

もしPandocがなければdocutilsを拡張することを考えただろうが、その場合はPureBuilder Simplyは今のように良いツールにはなっていなかっただろう。

PureBuilder Simplyが今ほど素晴らしいツールになったのは、Pandocがあったからこそだ。

Mimir Yokohamaに対して行われている様々な拡張は今の所PureBuilder Simplyに対して適用されていない。 これは、PureBuilder Simplyが生成するものに対する機能ではなく、Mimir Yokohama固有の、そしてテンプレートとCSSによるものだからだ。

だが、いくらかでも一般化してPureBuilder Simplyに還元していければと思っている。 PureBuilder Simplyのエコシステムの充実は普及には不可欠だろうから。

Mimir Yokohamaに「いいね機能」「コメント機能」を追加

概要

Weekly 10000PVを達成して機能強化に力の入っているMimir Yokohamaのウェブサイト。

連続の機能強化でついに「いいね機能」と「コメント機能」が追加された。

実は先日の「お問い合わせフォームの実装」は単にその機能を実装する最小限ではなく、簡単なアプリケーションを実装できるプラットフォームになっており、 アプリケーションを追加する条件が整っていたのだ。 また、そのためのテストもしてあった。

そのため、実は今回コード追加はわずかで、両方合わせても23行ほどにとどまる。 ごく簡単だが、確証が持てないためにテストと本番環境のための修正を行ったりして結構な時間がかかった。

これに関してはみるべきところはあまりない。 受け取ったパラメータをファイルに書き込めば良いだけだからだ。

ちなみに、連打しやすいアプリケーションを入れるために連打の対策もサーバーにしてあった。 実は先のパフォーマンスチューニングはこの対策によってパフォーマンスが低下してしまったため、これをカバーするついでに行ったものだった。

いいね機能の設計

いいね機能はごくシンプルだ。

Pandocテンプレートを使ってページタイトルを埋め込むことができるので、これだけ使うのであれば単にテンプレートの中にフォームを書けばいいだけ、ということになる。

実際はリファラ(Rack::Request#referer)及びユーザーエージェント(Rack::Request#user_agent), IPアドレス(Rack::Request#ip)も特定に使用している。

ポイントは「一度送信したら表示を変更、送信を無効化する」ということだ。

inputタグのdisabled属性を使うことで送信ボタンを無効化している。

そう、この機能はHTML上でインラインで書かれているのだ。 このような書き方はW3C的には推奨されないのだが、Googleは推奨している。 実際、これだけのためにJavaScriptファイルをロードさせることをしたくなかったので、インラインにした。

ポイントは

  • Legacy DOMにおいてフォーム部品は連想配列のようにアクセスできるようになっている
  • submitボタンのラベルはvalueである
  • disabledによってフォーム部品を無効化できる

である。

これらの処理は送信の「前に」行われる。 これは正しいことではないが、問題はない。 なぜならば

  • 送信できなかったからといってユーザーが修正するなどの手を入れる余地はない
  • 特に返信を必要とするものでもないので、送信失敗はクリティカルな問題でもない
  • サーバーエラーなどは表示されるようになっている

からだ。

また、画面変遷せずに送るだけ…というと、Ajaxで非同期に送るしかないように考えるかもしれないが、 実際は現代のブラウザは基本的に2XXステータスで空コンテンツを返すとページ変遷しないようになっている。

このような用途のために204(No Content)が用意されているため、204を返す仕様だ。 できるだけブラウザの標準機能に頼るようにしている。

このボタン、めっちゃgooglebotが押してくる…

コメント機能の設計

公開されるものではなく、sanitizeしなくても問題が発生しない設計になっている(単に文字列として扱う以上の取り扱いがなされる条件で使用しない)ため、非常に楽だ。

難しく考えるよりも、シンプルで挙動をちゃんと把握できていて、余計なことをしない方法をとるのが最も楽に、確実に、バグなく設計できる。

ただ、この機能に関しては「コメントフォームの表示」などが必要になり、JavaScriptが必要になった。 また、コメントするというあまりとらない行為のための部品であり、常にロードすることは非常に好ましくない。

そこでとった方法は「JavaScriptの遅延ロード」であり、「クリック時にelementを追加してJavaScriptを読み込む」という方法だ。

これでscript要素を追加している。

では呼び出されたあとどうしているのか…というと、こんな感じだ。

そう、コメントフォームは標準でHTMLに含まれていない。

パフォーマンス的にみても親切機能のためにもう重くなりつつあるドキュメントをこれ以上重くしたくないので、 「フォーム部分はJavaScriptがロードされたときにinnerHTMLで書く、という方法をとっている。

「クリックされたときにはじめて必要とされるのでドキュメントノードを追加してロードする」という手法は稀に使われるが、さすがにそれでHTMLドキュメントそのものを生成するというのはまず見ない手法になっている。

連打されたときのことを考えて、clickイベントを発生する値に特殊なプロパティを埋め込み、何度も実行されないようにしている。 なお、これは本当に連打したときだけ機能するもので、JavaScriptシングルスレッドなので一回このスクリプトに入っていまえばこのスクリプト中に割り込まれることはない。 だが、連打されるとイベントがキューに入ってしまうので何度も実行されてしまうことから、そうしたことがないようにしている。 シングルスレッドなので、このスクリプトが実行されているのにプロパティが設定されないうちにまた実行されるということはない。

元になるフォーム自体は存在していて、submitボタンを配置すればフォームの送信は可能だ。 そのため、submitイベントに対するイベントリスナーを設定するものはHTML上に静的に存在している。

その上で「フォームはやっぱり閉じられたほうがいいな」ということでボタンクリックに対するイベントの変更、及びボタンラベルの変更を行っている。

送信を行った場合はもうコメントフォームは使わないので、コメントフォームは非表示にして(削除はしていない)ラベルを変更している。

ラベル変更だが、input部品とは違ってbutton要素は子要素テキストノードがラベルになっているので、dataプロパティの書き換えによって変更している。

もともとHTML上でLevel0 DOMイベントを使っているので、スクリプト上でもLevel0 DOMによってイベントを変更している。 いつもaddEventListenerを使っていたので、珍しい。 イベントの削除はイベントにnullを代入するだけだ。

なお、HTMLで全て含めてしまえばJavaScriptは排除できるのだが、これ以上余計な要素を組み込むことはできれば避けたいためこのような仕様になっている。既にDOMコンパイルは割と重い。

また、デザインポリシーからいって、多くの場合余計なコメントフォームを常に表示させておくというのは美しくないとも思っている。 JavaScriptを使わずCSSでオンオフするようにもできるが、そうすると今度はボタンのテキストを変更するのが難しいし、連打や再送信を防ぐのも難しい。

このことから、なにがなんでもJavaScriptを使わないのが正義、ということでなければ、 このような付加コンテンツはユーザービリティの点からも素直に使うべきだと思う。

こちらもいいね機能同様、送信する前に状態を変更してしまい、成功すれば204を返す仕様。

Legacy DOM と DOM Level0 Eventに関して

Legacy DOMやDOM Level0 Eventについて意見をもとめられることがたまにあるのだが、私としてはあまり勧められないと考えている。

非常に簡単なので学習にはいいが、Legacy DOMはHTMLの構造に依存する。例えば

として、

とかやってしまうと、ドキュメントの構造が変わるたびに修正だ。

だが、「フォームにのみnameで使い」「フォームはW3C DOMで特定する」のであれば悪くない。

DOM Level0 Eventはイベントリスナーが1つしか登録できないためモジュール設計になっている場合や、なんらかのライブラリを使っている場合は使ってはいけない。 イベントを「追加」する場合はLevel2で、イベントを「設定」する場合はLevel0という使い分けも考えられる。 HTMLに直接書く場合はLevel0しか書けないので、その部品の基本的な動作と定義されているならLevel0でもいい。

ただし、その場合でもできればLevel2 Eventを「後から追加する」のが適切なので、DOM Level0 Eventの使いどころは今回のようなものが唯一だと思う。

Chromiumのインスペクタにいつの間にかモバイルデバイスインスペクションが入っていた

web分野の人にとっては当たり前の可能性もある知識だけれども、Chromiumのインスペクタにモバイルデバイス向けの検証機能がついていた。

Chromiumのモバイルインスペクタ

これをオンにすると、モバイル的な画面サイズとなり、同時にタッチエミュレーションが行われるようになる。 レンダリングが調整されるということはなく、あくまでこの2点が変更される。

モバイルインスペクタのガイド画面

Responsiveモードではサイズを柔軟に変更することができる。 実際に小さな画面ではどのように表示されるのかということをエミュレートできる。

また、デバイスごとの画面設定も可能。 候補は少ないがiPhoneは入っている。

機種や回線をエミュレートできる

ここで言う解像度は論理ピクセル数である。 例えばiPhone8の画面の実ピクセル数は1334×750だが、668×375と認識されている。

これはスケーリングによって1pxを実際の1ピクセルと対応させない、というもので、先日書いたので参考にしてほしい

拡大率は画面表示のサイズを変更するもので、リアルサイズに近づけることで物理的な大きさもチェックできる。

上部にあるガイドは各種デバイスに合わせた表示状態をエミュレートできるものになっている。 クリックするとResponsiveの状態で各プリセットにあったものになる。 Laptopが1024px, Laptop Lが1440pxとなっており、この上に4k2560pxもテストできるようになっている。

また、ネットワーク状態もエミュレートできる。 低速な回線ではどれくらいロードにかかるのかということもテスト可能だ。 offlineはページをロードしたあとにofflineにし、Ajaxが使えない状態をテストすることができる。

Low-endでMimir Yokohamaが4秒程度なのに対し、Chienomiは15秒ほどかかったので、その高速さにだいぶ満足している。

デバイスの論理ピクセル数なんて調べるのはとても大変なのでプリセットが入っているのはとても嬉しい。 サイトの設計にもよるが、私の設計だとアップロード前に十分なテストが行える。

メッセージフォームのサポート (Nginx + FastCGI + spawn-fcgi + Rack + Ruby)

あらまし

Mimir Yokohamaでついにお問い合わせ方法として「メッセージフォーム」が追加された。

なにがついになのか、なにをドヤっているのかと思うかもしれない。 まぁ、ドヤってはいないのだが。

実は私はかなり長い間ウェブアプリケーションをほとんど作っていない。 そして、今まで私が作ったウェブアプリケーションは、専用サーバーを持つサーブレットタイプか、もしくはCGIだった。

馬鹿にされがちなCGIだが、利便性は高く、頻繁にアクセスする性質を持たないアプリケーションには適している。

そして、そもそもウェブアプリケーションを作っていなかったのは、私が「事前生成戦略」の研究と実験に注力していたからで、 どちらかといえばウェブアプリケーションからは離れる方向にあった。 そして、ウェブアプリケーションを必要とするとしても大部分は静的ページとして提供できる方式を目指していたため、CGIで十分事足りたのである。

ちなみに、これまでウェブサーバーは

  • Apache
  • lighttpd
  • delegate
  • Nginx

という経過をたどっている。 Apacheは言うに及ばずlighttpdとdelegateはApacheよりもCGIが簡単だったので、「ほぼCGI」だった。

だが、時代は変わった。NginxはCGIをそもそもサポートしない。 私も新しい時代に対応する必要がある。

ちなみに、この作業は次の仕事のための実戦テストという意味合いもあった。

方針を考える

最も話が速いのはFastCGI Wrapである。

NginxはFastCGIをサポートしている。 FastCGIはプログラムをデーモンのように起動しっぱなしにする。

だが、通しで実行するプログラムとデーモンではそもそもの前提が違う。 そのためCGIプログラムをFastCGIとして動かすのはそれなりにハードルが高い。

そこでFastCGI Wrapの登場である。 FastCGIとして利用されるプログラムをFastCGI Wrapにする方式だ。 このラッパープログラムは要求に合わせて都度CGIプログラムをCGIインターフェイス経由で起動する。 結果的にFastCGIの意図は無視して従来型CGIを動作させるようにするというものだ。

この方法は結構出てくるのだが、基本的には既存のCGIプログラムを動作させる話である。

個人的な感覚としては、無駄なプロキシを噛ませるような方法を使ってまでCGIに固執したくない…というか、実はfcgi-wrapってそれなりにめんどくさい。

だったらFastCGI直というのもありかなぁ、と考えるわけだ。

ところが、やっぱりFastCGIはデーモン状のプログラムを想定しているわけで、やはり前提が違う。 要求として割と複雑なのか、デーモン化に関してはspawn-fcgiに担ってもらって、さらにRackを使う、というのがどうやら主流らしい。

だいぶ話が複雑になってきた。

サーバーはNginxである。NginxはFastCGIインターフェイスを経由してFastCGIプログラムにパラメータを渡し、応答を受け取る。

FastCGIプログラムはデーモンである。 Rubyでは次のようにしてFastCGIプログラムを書くことができる。

あるいは、CGIライブラリ互換インターフェイスを使うことで、#each_cgiの中身はまるっきりCGIと同じにすることもできる。

spawn-fcgiはこのデーモン部分を担う。 つまりeachしてる部分を担ってくれるわけだ。

プロセスとしてCGIインターフェイスで起動するわけではないので、fcgiwrapほどの互換性はない。 感覚はCGIに近いが、インターフェイスは意識する必要がある。

Rackはミドルウェアと呼ばれている。これはまずFastCGI抜きで話そう。

Rackはインターフェイスを担っている。 今までプログラムはCGIなり、あるいはFCGIなり、さらには各種フレームワークやサーブレットの様式(例えばSinatraとか)で書いていた。

Rackはこれらの違いを吸収するモジュール設計のものだ。 Rackに準拠したプログラムを書いておけば、たとえ愛用のフレームワークがディスコンになっても、サーバーが変わっても安心、というわけだ。

だが、Rack自身はサーバーではないからサーバーがいるのだが、Rack組み込みのサーバーというのはもう完全にRuby世界の住人だ。 だってRackはRubyのWebアプリケーションインターフェイスだから。

Passengerというソフトウェアがあって、これはwebサーバーのモジュールとしてRackに対応する。 Apacheでは比較的簡単だけれど、Nginxだと結構きつい。

そこでRackに対応したサーバーを立ててサーバーとサーバーでやりとりさせる、という方式がすごく現代的。 直接にRack経由でプログラムとやりとりするのはRackに対応したサーバーだけれど、Rackに対応したサーバーにwebサーバーとしての機能を持たせると大変なので、「本物のwebサーバーに矢面に立ってもらって、RackサーバーはあくまでRack対応に特化」というわけである。

Rackに特化したサーバーとしては(別にRackだけではないんだけど)、Webrick, Mongrel, Puma, Thin, Unicornあたりがある。

しかしRackでやりとりする方法があればいいので、FastCGI + Rackという方法もある。 それはRack側でFastCGI経由で受け取って、応答するためのハンドラが用意されている。

つまり、Unicornのようなサーバーを立てる代わりの手段としてFastCGIが使える。 FastCGIもデーモンを必要とするので別にFastCGIにすることで間に挟まってるものを減らす効果はない。 ただ話が楽になるだけである。

Unicornはむちゃくちゃ速いので、UnicornでUnixドメインソケットを使えば形式とししてはspawn-fcgiでUnixドメインソケットを使っているのと一緒だし、やっていることははるかに高度になる。 これが超モダンなやり方である。

が、あえてのFastCGI。 理由は管理する要素数を減らすためである。必要がないのにいかついものを使うことはしない。 これはサーバー運用のコツでもある。

なお、Rackに関してはかなり情報が少ない。 なんらかのフレームワーク…というか、ほぼRailsのバックエンドとしてのRackの話だけで、Rack単独の話ってない。 そして、FastCGIを使う話もない。これもだいたいなんらかのアプリケーションが「使ってる」あるいは「使わせる」話になる。

なんというか、みんなそんなに自分でプログラム作るってことをしてないのか… 世の中エンジニアたくさんいるのに、WordPressとRailsだけで満足なのか…

そんなわけで情報が猛烈に足りていない中、FastCGIとRackについて勉強することになったわけだ。

なお、Nginxでアプリケーションとやりとりする方法に関してはDiscourceで散々やったので経験済みだ。

なぜRackなのか

もちろんこのことからもわかるようにRackはなくても構わない。 spawn-cgiも使用せず単独のFastCGIアプリケーションを開発するのは容易である。

私が気にしたのはRubyのfcgiライブラリは2013年から更新が止まっているとい点だ。 また、Arch LinuxではfcgiライブラリはAURにもなく

# gem install --no-user-install fcgi

とするよりない。

ベーシックな機構であるFastCGIそのものが廃止になるようなことは考えにくいが、NginxのCGIの扱いのように消極的なサポートへと変遷する可能性はある。 その場合にアプリケーションの書き直しが発生してしまう。

Rackは現在主流であり、新規採用例も多い。 Rackが廃止になると影響を受ける範囲も非常に広いので今後10年は安泰だと思われる。

そこでFastCGI+Rackという構成にしたわけだ。 この場合でもRackはFastCGIをネイティブサポートしているわけではく、fcgiライブラリを使ったハンドラを同梱しているだけなのでfcgiライブラリは必要となる。実はこれを回避したかったのだが、結局はできなかった形だ。

とはいえ、この状態であればFastCGIを捨ててUnicornに移行するのも難しくはない。

とりあえずやってみる

Nginx

location / {
    root /var/www/testapp;
    fastcgi_pass /var/run/fcgi-testapp.sock
    fastcgi_index testapp.rb;
    include fastcgi_params;
}

Rack Application

Requestのほうはインターフェイスに絡むけれど、 Responseは単純に#finishでRackに沿った配列を返すための便利クラス。なくてもいい。

spawn-fcgi

# spawn-fcgi -U http -s /var/run/fcgi-testapp.sock /var/www/testapp/testapp.rb -n

試してるうちは-nつきにしてフォアグラウンドで実行するのが楽

実用的にする

起動スクリプト

forkingなので停止・再起動の制御のためPIDファイルを作る。

Systemd Unit

[Unit]
Description = FastCGI Rack Test Application
After = nginx.service

[Service]
Type = forking
PIDFile = /var/run/fcgi-testapp.pid
ExecStart = /usr/local/sbin/fcgi-testapp.bash
ExecStop = kill $MAINPID

[Install]
WantedBy = multi-user.target

forkingなので$MAINPIDがそのままでは使えないため、PIDFileで指定しておく。 Nginxのあとに起動しておいたほうがいいような気がしたけど、なくても構わない。 アクセスが激しい場合は逆にNginxの前に起動したほうがいいだろう

spawn-fcgi自体にはアプリをリロード、再起動するような機能はない。

おまけ

S-NailがSubjectも本文も、UTF-8をちゃんとエンコードしてくれるのですごくびっくりした。

「mailxとは違うのだよ!!!」ってことか。 さすがSMTPやPOPやIMAPにも対応しているだけのことはある。

ここの部分(MIMEエンコーディング)も自分でやるつもりだったので、かなり省力化された形。

今回の構築は他にも色々やったのだけれど、共有して意味のある部分はこれくらいのものだろう。