LINEオープンチャットに先を越されたか オープンチャットコミュニティへの想い

この話は今後を見据えた今の思考をまとめたものに過ぎない。

チャットについて

チャットにかける思い

チャット。

今の人にはあまり馴染みがないかもしれないが、インターネット黎明期のメインコンテンツがチャットだった。 ちなみに、もう少し前から見ると「掲示板」のほうがメインであり、ネットニューズを含めてポスト型のコミュニケーションから、パソコン通信時代を経てチャットのほうが追い越していく形だった。

私がはじめて経験した「画面の向こうの世界」は、the Internet上でのチャットだった。 1992年のことである。

私はあまり人と接さない子供だった。 幼少期は比較的コミュニティの濃い空間にいたが、「なじまない」という気持ちはとても強かった。

1992年に私が呼ばれたとき、私の扱いは「子供」ではなかった。ひとりのハッカーとして呼ばれたのだ。 技術的には未熟もいいところであったから、実際に対等なものであったのか、正直なところ今や私は覚えていない。10年くらい前までは結構はっきりと記憶していたと思うのだが、今や仲間たちの顔も名前も思い出せない。 そもそも、私の脳はそうしたことを思い出せるようにできていない。

そして、そのときのことは私にとって「衝撃な体験ではあった」のだが、あまり私を私たらしめる要素ではない。 それは、幼い頃のプログラミング体験が今の私には何も活きていないということと同じだ。

どちらかといえば、2000年に自宅でインターネットができるようになったとき――そしてチャット全盛期でもある――ようやく私の言葉が受け止められるという歓びに打ち震えたことこそが原体験となっている。 やがては結局、想いを共有できる者などいないという事実にぶち当たるわけだけども、少なくともそれまでのように言葉を尽くす意味もないということはなくなった。

結果、私はチャットにのめり込んだ。 real worldでの生活においては価値観を共有可能なコミュニティに巡り合わなかったということもあって、チャットは同時に私にとって貴重な出会いの場でもあった。 今私は誰とも触れ合わず暮らしているが、チャットがなければ昔もあまり変わらなかっただろう。そして、そうであればそれは今とは意味が全く異なる。

そのチャットも、コモディティ化によって崩壊する。 残されたのは、極めて限られた層だけが利己的に振る舞う場所としてのチャットだ。

復権はないのか、ずっと模索してきた

観測を続けていれば、インターネットの変化はよくわかる。 そして、人々が――特に若い人が――ネットに対して既知のコミュニティ以外を求めなくなってゆくこと、そして一方的なコミュニケーションのドッヂボールと化していくことは、痛いほど感じられた。

疑問はつきない。

人は本質的にコミュニケーションを求めるものなのではないのか

誰もが自分が暮らす狭い世界であてがわれる関係性だけで満足しているのか

――世の中誰もがリア充なのか?

もっともっと多くの人とめぐりあい、気の合う者、分かち合える者をみつけ、たくさんの言葉を交わしたいとは思わないのか?

少なくとも私は真のマイノリティだから、インターネットがなければ分かち合える言葉を持つことはない。 例えば、「持てる技術の限りを弄ぶ求道者」も、「知識と関心で世界の観測を望む者」も、「自己属性の有利ではなく均衡を望む者」も、普通に暮らしていては出会うことができない。 そう多く浮かぶわけではないが、今の知己の面々は実際、かけがえのないものだと私は認識している。

人は会話を望まないのか、チャットは人にとって夢のようなツールではないのか。 その想いはずっとあった。

現実は厳しい

チャットに限らないのだが、コミュニケーション系のコンテンツというのは非常に難しい。

一番の理由は「参加者の量に依存するから」である。 開始時は0にならざるを得ないので、コンテンツ価値がない状態からしかスタートできない。 そして、人の集め方を間違うと取り返しがつかない。 TwitterとLINEがどれほど腐っていても容易にはやめられない理由でもある。

技術的にも結構難しい。幅がひろすぎる上に、マッシヴコンテンツの対策というのは、経験しないことにはわからない。 Twitterは秒間25000ほどのツイートをさばくという。今では減少しているそうだが、携帯電話キャリアのあけおめメールのさばき方も並大抵ではないだろう。 IIJは甲子園野球の中継をホストしているが、550Gbpsを配信しているという。

Twitter以上の活発さを目指すのであれば技術的ハードルも高いなんてもんじゃない。

普通のHTMLチャットソフトの再実装(元ソフトウェアはTeaChat)からはじまり、このPerl5(オブジェクト指向), PHP5, Ruby 1.8, MySQLバージョン、sqliteバージョン、GDBMバージョン、ファイルシステムツリーバージョンと幅を広げ、 さらに独自実装ではNode.jsバージョン、Ruby emバージョン、事前静的生成バージョン(リードは静的HTMLで、送信によってHTMLを更新してしまう)が誕生し、EventMachine以外にもZeroMQを使ったもの、Rindaを使ったものなどとにかく色々と試した。 ZeroMQバージョンとRindaバージョンはウェブではないバージョンも登場した。

今に至るまで納得できる実装は登場していない。 作っている間に世の中の要件が変わりすぎるからだ。 「始めればついてくる」という確信があればとりあえず動くものを作っただろうが、そちらに関しては全くのノーアイディアだったから、それすらもしなかった。

例えば、成瀬さんは「チャットにログインがあるべきではない」と考えている。 一方私は「チャットに対してアクティブな状態にすることで「会話しよう」という明確な意思を形成できる」と考えている。 最高のチャット体験とはなにか、追い求めるほどに難しい。

いや、チャットコンテンツの主体は人だから、そのあたりはどうでもいいとも言える。 だが、「使ってもらえるチャット」である必要があるのだ。 だから「使い始める動機」を与える必要がある。

究極のUXを求めて

現状の仕様

RindaとZeroMQを中心とした複合的な技術になっている。フロントエンドはWebSocket+EventMachine(em-websocket)である。 繋ぎっぱなしなので、恐らくあまり大きな負荷には耐えられない。

データは極力クライアントサイドによって処理するという仕様であり、クライアントサイドJavaScriptに大いに依存している。

概念としてはログインがなく、「チャンネルの購読」という方式になっている。 channel subscribeするとチャンネルに対する更新を受け取ることができる。 チャンネルは無軌道に作成できるわけではない。

表示はoverall timelineとchannelの2種類。 過去ログ機能はなく、あくまでアクティブ中のメッセージが読めるだけ。

この表示はクライアントサイドのフィルタになっていて、channelを選択すると当該チャンネル以外のメッセージが非表示になる。

発言を行う場合、表示がchannelであれば単純にメッセージウィンドウを出して発言するだけ。 overall timelineの場合はメッセージウィンドウを開くときにチャンネルを選択する。

アカウントとログインは、アカウント認証時にeメールを必須とし、以降はログインのたびにeメールに書かれたPINを入力する。 パスワードはなく、eメールアカウントで管理することになる。

これとは別にトラディショナルなHTMLウェブチャットもある。 トラディショナルと言いながら実装は普通ではなく、

  • CGIによって動作するRubyスクリプト
  • 表示はiframe上の静的なウェブページ
  • チャットページはmetaタグによる自動リロード
  • チャットポップアップキー、またはチャットウィンドウアイコンによって発言用のウィンドウをポップアップする。これは親フレーム側(静的HTML)に属している
  • 同ウィンドウの送信先はチャットCGI
  • チャットCGIはデータベース(GDBM)を更新すると共にHTMLを書き換える

という仕様である。

実はあんまりよくない

Twitterとチャットの中間というか、まぁマストドンに近い感覚になっているのだけど、もっと単純なほうが使いやすい、と感じてしまう。

チャットUIのベストのあり方、というのは難しいが、PCではテーブル型(発言者と発言の横並び)、スマホでは定義リスト型(発言者の下にインデントして発言)が見やすいように思う。 さらにいえば、定義リスト型で長さによって横に並べるか下に置くかを動的にするのがベスト、かもしれない。

UIデザインに関しては色々と検討しているが、少なくともLINEのものは「悪くはないが、最善ではない」というところにある。 大人数の場合は追いにくくなるので、もっとエレメントを詰めて表示したい。

恐らくデザイン的にはDiscordのチャットが素晴らしい。

しかし、いずれにしても単純な連続的会話ストリームであるほうがよく、つまりはDiscordやSlack、あるいはIRCのような会話が望ましい。 結局のところ、TwitterのようなSNS形式が会話の環境として望ましいわけではない。それは、TwitterのDMがTwitterのUIとは別の形式を持っていることからも明らかだろう。

もうひとつの問題が、「ライフの長いチャンネルは淀む」ということだ。 チャンネルに支配的な発言者がいるとそのチャンネルは腐敗していく。これはYouTubeのチャット欄などでも発生する事情で、常連が支配的に振る舞うと、結局チャット全体が支配されてしまう。 だから、チャットチャンネルのライフはある程度短いほうが良い。だから、固定的なチャンネルというのは良いものでもない。

結果的には、Twitterがチャットの進化系であるという意見を元にTwitter的なやり方を取り入れて(もっと言えば、IRCや2ちゃんねるなどの「流行った手法」の踏襲)みたものの、結果としては「昔のチャットのほうがよかった」という結論に至ってしまう。

それはある意味当然で、昔のチャットというのは、他のことが手がつかないとしても会話に集中できる環境づくりが意識されていた。 対して、近年のLINEのようなコミュニケーションツールは昔のケータイメールの延長線上、つまり「メッセージを残す」性質のものになっている。 だからチャットでは通常あまり重要ではないタイムスタンプや既読マークが重要になるし、会話のフローを一覧しやすいようにもなっていない。

結局のところチャットとして望ましい仕様というのは非常に古典的なものだという結論に到達してしまうのだ。

UI部分

実は1:1とパーティチャットにおける望ましいUIというのは全然違う。

1:1だとDiscordのUIは相当に理想に近いところにある。 PC版だと他のサーバーや他のダイレクトメッセージを開いたり、通話をかけたりといった操作系がわかりやすく配置されている。 改行が可能であることは良いこととは言い難いが、そのためにMarkdownを用いた修飾が可能であることは利用の幅を広げてくれる。ただ、純粋に「チャット」であるならばそのあたりは抑制的なほうがいいだろう。

スマホ版だとメッセージ表示の余地が大きく、LINEと比べてすっきりしていて見やすい。 欲をいえば上部にあるアイコンやメニューなどは左右スワイプで出したい。

横長でもメッセージ幅を広げてくれるため見やすい。アイコン表示も見やすく、アイコンの存在がメッセージ表示領域を圧迫していない。 PC版ではページ外に、モバイル版ではチャットウィンドウ上にタイピング中のインジケータを出す。 これはSkypeあたりがやり始めたことだったような気がする。

スマートフォンならXabberも悪くないと思うのだが、これだとLINEやTelegramとほぼ一緒。

DiscordのUIは洗練された良いものだが、古典的なメッセンジャーUIであるPidginを見ても別に悪くないと感じる。 会話が盛り上がってくれば表示に改善できる余地がある点も気にならない。

それどころか、古典的なHTMLチャットですら割とアリだ。 現代のコンピュータと回線だと速度的にも速くなって非常に実用的。

パーティチャットの場合は大量のメッセージが流れることになるため、より一覧性が高く、スクロール時に見失いにくいデザインが望まれる。 インスタントメッセンジャーアプリの中ではKopeteが非常に不満な安定性ではある(よく落ちるし、接続もなかなか確立できず、メッセージの受動的受け取りができない。さらにいえば、コンタクトリストがどんどん増える)のだが、UIは最高に望ましい。 邪魔にならないアイコンと、コンパクトな表示が優れている。ただ、名前とメッセージの間が空きすぎていて、Discordよりも間延びしているのが残念。とはいえ、PC向けのパーティチャットの基本的な最も良いUIがこの方向であることは間違いない。

加えていうなら、「色分けしたい」というのはある。 ここでは「自分とそれ以外」でヘッダー部分の色が変わるのだが、メッセージ色なり、名前色なりといった部分がユーザーごとに違うようにして、アイコンや名前を確認しなくても誰が発言したかわかるようにすることと、自身の発言だけ背景色をつけることで明確にすることだ。

スマホ版でも基本的な方向性には変わりないが、チャットウィンドウ以外の「なにも表示しない」のがいいのではないだろうか。 1:1では常に自分は発言する状態にあるが、パーティチャットの場合その瞬間に発言しようとしていない人というのが多い。 だから発言用のウィンドウも非表示。チャットウィンドウだけ。参加者一覧とメニューアイコンを左から右へのスワイプで、発言を右から左へのスワイプで出す、というのはどうだろうか。 もしくは小さな発言用アイコンをフロートさせてもいいのだけど。

スマホで発言ウィンドウ(というよりもソフトウェアキーボード)を出した状態での表示領域の小ささはいかんともしがたい。 発言ウィンドウ表示は1行に抑えるとして、それでもチャットの表示は少ないので、発言ウィンドウを開いている間は表示更新はしないのがよかろう。

チャット体験部分

チャットの体験としては、「ライフの短く、固定性のないチャンネルだが、匿名ではない」が良さそうだ。

現存するものとしてはランダムマッチングのものがあるが、さすがにこれはライフが短すぎる。 また、よりライフの短い人と非常にマッチングしやすくS/N比が実際以上に悪化してしまう。

このことを考えると、

  • チャンネルは自由に立てられるが
  • チャンネルのカテゴリは大枠で制限されており
  • 検索によって発見する
  • チャンネルは接続が0になるか、一定時間以上誰も発言しないとクローズする

あたりが良いのではないかと思う。

ただし、これだと部屋の作成がまともにできないので、作成後一定時間はライフを維持できるようにするか、作成後joinされるまではある程度の時間待ち受け状態であり続けられる(検索対象になる)という形で良いかと思う。

これにroom limitを加え(固定値でもいいし、5から20などの範囲で設定できても良い)、limitを迎えたチャンネルは検索対象にならないようにする。joinできなくするかは要検討。恐らくjoinできなくする必要はない。

これで交流はある程度流動的になるし、どうしても固まる人たちはむしろ固まってくれたほうが良い。

kickは与えず、チャンネルマスターも設定しない。 それをすると「開設者がチャンネルマスターとして支配的に振る舞う」という事態が発生するからだ。

ユーザー関連機能は厳し目にしないとひどいことになってしまう。

厳格なユーザー認証(恐らくSMSが良い)を必要とし、ユーザーに対してはプロフィール機能を用意する。 このプロフィール機能は記入は任意であり、主にはチャットユーザーがそのチャッターに関する情報を確認するためのものである。 実は、「はとこみ」設計時はこの機能を中核として考えており、「ユーザーがあり、コミュニケーションを取る」ような形であったが、それと比べると抑制的である。

チャンネルに関してカテゴリ違いや禁止事項への抵触に対する対応はかなり厳しく行う。 一方、「通報」に関する対応は、「通報件数が多いから」のような対応はしない。

どちらかというと「ブロック」を上手に使う方向で考えている。 このあたりはYouTubeチャットと同じか。

運営リソースはかなり必要になりそうだ。

そして、これはLINEオープンチャットのように、無数の人が最初からいる前提である。

LINEオープンチャットがユートピアとなりえない理由

時代を変革するかと思われたLINEオープンチャットだが、致命的な問題が見つかったので、そこまでのことではないと判断することができた。

その致命的な問題が、「過剰な連絡先交換の拒絶」である。

現在、社会的な理由でリスクを避けるためにも、また制度的(法的)な理由でもそのようにしたいということはわからないではない。 だが、これは致命的なミスである。

このような扱いは、結局のところ「匿名で荒れる」という状況を作り出すことになる。 その場での発言が全てなので「別に何言ったってこの場のことだし」となるし、結果2ちゃんねるやニコニコ動画のコメントのように非常に品位の低い一方的な言葉の投げつけが行われることになる。 そんな環境ではまともな会話が成り立つことは極めて少なくなるし、生産的な場になりようがない。

交流の場たらしめるには匿名にしてはならないのだ。 それは、ずっとチャットを含めコミュニケーションをずっと眺めてきて強く感じることだ。

そして、匿名にすること、ここが「他のコミュニケーションとは違う」という扱いをすることによって「画面の向こうに人がいる」という基本的なことを意識しなくなる。 だから何をしてもいいという理屈を振り回すようになり、人を傷つけることに対して抵抗をなくしてしまう。 必要なことは匿名化ではなく、「on your risk」を明確にすることだ。

連絡先を交換することも、あなたのリスクにおいて、それが結果どうなったとしても他者に責任を求めることはできないし、してはならないということを承知の上で自身で選択すればよい。 自身の振る舞いには常に責任があるということを明確にしないと、ただただ悪意と攻撃ばかりが増幅していく。

LINEオープンチャットは実際に蓋をあけてみれば、あらゆる点に対して思慮が足りなかった。 チャットの運営はとても大変なものであり、それは文化足り得るからこそ慎重さも必要となる。

確かに、LINEでチャットをはじめれば多くの人が利用するだろう。 だが、それが意味するところ、それをすることで実現できることを十分に測らなかった、そう感じる。

もちろん、これはある種イデオロギーの対立である。 私が正しいと信じるものに賛成はすまい。 でもだからこそ、それは私が求めるユートピアではないと、そう感じるのだ。

正規表現マスターの「コツ」

「正規表現がわからない」という人は結構多い。

私としては、正規表現(Perl regexp)の習得は特に苦もなく、長大な正規表現もすんなり書けるようになったのであまり実感はないのだが、どうも正規表現の記述に関しては得手不得手がはっきり出る部分であるようだ。

私が正規表現を教えてきた感覚で言うと、正規表現はむしろプログラミングに関してある程度知識がある人のほうがハマりやすい。 プログラミングが関数型言語を除けば手続き的であるのに対し、正規表現は宣言的であるため、その切り替えができず、概念がごっちゃになる部分が大きいようだ。

正規表現を理解するには、正規表現が宣言的であり、どのような概念の集合なのかということを理解するのが重要だ。 そして、それだけを理解すれば単純な要素の複合であるため、全く難しくはない。

ここではMimir Yokohamaのクラス2授業であるプログラミング系科目で教えている正規表現の内容から、特に肝となる部分のエッセンスを抽出し、紹介したいと思う。 察しのいい、定義によって明らかにされれば論理的導出で理解できる人にとってはこの内容が十分助けになるだろう。

より具体的な説明や、詳細な大切、具体例などが欲しい人は、ぜひMimir Yokohamaを頼って欲しい。

正規表現の要素

基本

正規表現の要素はBREの場合

  • 文字
  • グループ
  • 文字列
  • 位置

が存在する。

EREの場合は「文字列」がない。

Perl RegexやOniguruma/Onigmoの場合、「パターン」が追加される。 まぁ、さらに「条件」や「実行」や「演算」が追加されたりもするのだけど、そこは置いておく。 正規表現わからないと言っている人の話題はそこではないだろうから。

文字

文字は正規表現の基本要素であり、正規表現の考え方としては文字が並ぶことで構文となる、と考えられている。

正規表現は文法の条件の記述であり、文字というのは固定の文字ということではなく、条件に合致する文字を示すものである。

通常の文字、例えばaは、aという文字だけが条件に合致する文字があることを示す。

[abc]a, b, cのいずれかが条件に合致する文字であるということだ。

これは例えば文字クラスやUnicodeスクリプトにおいても、どのような文字が条件に合致するかということを示しているだけであり、文字には変わりない。

グループ

グループは文字条件を1文字以外に拡張するものである。

文字はあくまで文字として定義されたものが1文字に一致するのだが、グループ化されるのは正規表現であるために、 0文字以上の 任意の文字列が条件に当てはまることになる。

量は文字, 文字列, グループ(パターン)の反復を示す。

単純に「〜がn回あって」という話だと考えていい。もちろん、場合によっては「n回またはm回」であったり、「n回以上」であったり、「n回以下」であったり、「n回からm回」であったりする。

文字列

文字列はキャプチャによって得られる既知の文字列である。

例えばBREにおいて

\(ab\)\1

というと、ababということになる。

文字列はパターンでなく、あくまで文字列なので、

\(a[ab]\)\1

というと、ababaaaaにはマッチするが、aaabにはマッチしない。

多くの人が理解していない部分として、文字列は量指定子の対象である。だから

% print ababababbbabab | grep -o '\(a[ab]\)\1*'
abababab
abab

という結果になる。

位置

位置は「ここがこういう場所である」ということを条件とするものである。

普通のBREの場合、「行頭」(^)と「行末」($)の2種類しかない。 GNU grepだと「単語協会」(\b)もあったりする。

Perlの正規表現にあるのは位置をパターンで表すことができる幅ゼロアサーションだ。 幅ゼロアサーションはパターンであるために混乱を招くかもしれないが、「…という場所」ということを述べていると理解すれば難しくない。

パターン (強敵)

さて、最大の強敵がパターンだ。Perlだと5.14から追加され、Onigurumaにも目玉機能として入っている。

正規表現そのものがパターンだから、パターンの中にパターンを表現するということは、「再利用」や「再帰」ができるということである。

再帰だと混乱する人が多いので再利用について述べると、例えば

nnn-ACnnn-Xnnn-n

という文法が存在するとしよう。(nは数字)。もちろんこれは、

\d{3}-AC\d{3}-X\d{3}-\d

で表現できるものである。

パターンを正規表現中で利用すること自体は、「グループの量指定」によって可能である。 ところが、これはあくまで「グループで示されるパターンが連続する」場合であり、「グループで示されるパターンが断続する」場合や、「グループで示されるパターンが内包される」場合には利用できない。

これを可能にするのが部分式呼び出しである。 これを使うとOnigmoでは

(\d{3})-AC\g<1>-X\g<1>-\d

と書くことができる。 \g<1>というところが1番目にキャプチャされたパターン(\d{3})を意味する。

ここでは\d{3}なんていうものすごく単純なパターンであり、別に嬉しくないように思うが、これはものすごく複雑なパターンが繰り返す場合には相当タイピングを節約できる。

Onigmoでは名前付き捕獲式を使うことができる。

(?<num>\d{3})-AC\g<num>-X\g<num>-\d

\g\kで悩む人もいるが、\kは単純に捕獲された文字列である。 これは既にあったものだ。だから、

% ruby -e 'ARGV.first =~ /([ab])\k<1>/ and puts $&' abaaa
aa

である。([ab]\k<1>)([ab])\1に等しい。

そして、部分式呼び出しはパターンであるから、

ruby -e 'ARGV.first =~ /([ab])\g<1>/ and puts $&' abaaa
ab

であり、これは結果的には([ab]){2}と書いても同じである。もちろん、その場合は間に何かをはさむことはできないが。

つまり、パターンが利用できる正規表現エンジンにおいては、「文字列の再利用」だけでなく「パターンの再利用」が可能になっている。 だから結果的にグループが「パターンの宣言的定義」にもなっているのだ。

正規表現の考え方

正規表現は文法を示すものである、ということを忘れてはいけない。 「Xはこのような文法を持っている」ということを記述するものである。

正直なところ、BNFなんかよりは遥かにわかりやすく、簡単だと思う。

例えばウェブアドレスの場合、httpまたはhttpsスキームを持つわけだが、そのあとはあまり変わらない。だから

https?://(?:\g<user>:\g<password>@)?\g<domain>/\g<path>(?:\g<param>(?:\g<param>)*)?

のように書くことができる。

「一致する文字列」というふうに考え出すとドツボにハマる。 正規表現はその概念を定義するものだ。

プログラミング上では、値よりもクラスの定義のほうが近いと思えば良いだろう。 クラスの定義は「Xというクラスは以下のようなものである。メソッドa、メソッドb、インスタンス変数xをもち…」というように行うものだが、 正規表現もまた「re1は以下のように正規表現によって表すことができるものである」と述べているわけだから。

ちなみに、この場合では各捕獲式がこの正規表現中で定義されていないため、正規表現のコンパイルエラーになる。 だが、パターンに名前をつけられるのであれば、事前に部分的文法を定義しておきたいものだ。 Perl6のRulesはそのようになっており、「部分式呼び出し」を中核に据えたものになっている。なので、よりわかりやすく宣言的だ。

ただし、Perl6のRulesは正規表現とは記法に互換性が 全くない ので注意してほしい。

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のほうがずっとやりやすい。

Manjaro Linux, Arch Linux共にDiscordがCommunityパッケージに

AURからなくなったDiscordの行方

先日の記事でDiscordがAURから削除され、Snappyを利用するように変更されたということを記事にしたが、2018-08-13 MANJAROのstable updateでcommunityパッケージに追加された

もう少し解説

ManjaroのリポジトリはArchとは独立しており、そのパッケージングポリシーも少し違う。 一方でArchのユーザー投稿リポジトリであるAURのパッケージのほとんどが利用できる程度には互換性がある。

原則として、AURにはArchの公式パッケージとして存在するものは投稿できないし、AURのパッケージがArchのパッケージになる場合、AURから削除される。 一方、Manjaroにおいてはもっと広く、「多くの一般ユーザーが欲しがるパッケージ」が公式パッケージに加わっているため、AURと重複するパッケージというのが存在している。これは時々トラブルの元になる。 例えば公式パッケージから削除されたときにAURパッケージがインストールされてしまうなどだ。

今回のAURからの削除は、重複するパッケージを設置しないというArchのポリシーに基づき、Snappyによってインストールすることを推奨するものかと推察された。 これに対しManjaroがAURから削除されたパッケージを「必要としている」と判断して独自にDiscordをパッケージングしたという理解だ。 だが、実際にはArchのほうもcommunityパッケージとしてDiscordが追加された

extraではなく、サポートの手厚いcommunityパッケージというのは驚きだが、これは「LinuxerにとってコミュニケーションツールはDiscordが標準となっている」ということでもあるのだろう。

やはりSnappy版よりもフォントレンダリングをはじめ全体的に良いので、よかったと思う。 ちなみに、Discord自体、最近debパッケージだけでなくバイナリパッケージも配布している。

Stellaの言語デザイン

Stellaの話、第三段。 今回は、特徴的な「Stella logicとDSL」の話をしていく。

正直なところ、Stella logicは仕方ないとはいえあまりスマートではない設計がなされている。 なおかつYAML形式だ。 確かに知識が少なくても書けるようには工夫されているが、その一方で労力、時間、そしてミス発生の余地という犠牲を払うことになる。

だが、このYAML形式、というのがポイントになっている。 その不自由は、与えられたものを使うだけの人にとってのみのものだからだ。

Stella logicの構造

Stella logicの肝となるのはcontext leafと呼ばれるものだが、これは

[ method, arg ]

という構造をしている。 methodはtests及びactionsが存在し、それぞれ「テスト名」「アクション名」と呼ばれている。 argの型はmethodによって決まっており、

[ method, arg1, arg2...]

のようにはならず、常に単一の値である。

例えば

[ match, String ]

のように定義されており、ものによっては

[ msg, (String|Array)]

のように定義されている。

もうなんとなくわかるかもしれないが、思いっきりLispである。 もっとも、Stella logicはなんとなくLispっぽいだけで純粋にLispスタイルなわけではない。

基本的にはcontext leafまでは次のように定義されている。

root.is_a? Hash
root.has_key? "Context"
root["Context"].is_a? Array
root["Context"].all? do |context_tree|
  context_tree.all? do |context_branch| 
    context_branch.is_a? Hash && context_branch.has_key? "tests" && context_branch.has_key? "actions" and
    context_branch["tests"].is_a? Array and
    context_branch["actions"].is_a? Array
  end
end

もっと具体的には次のようになる。

root = {"Context" = {"main" => Array.new} }
root["Context"]["main"].push({"tests" => Array.new, "actions" => Array.new})

有効なcontext leafまで書くと

root = {"Context" = {"main" => Array.new} }
root["Context"]["main"].push({"tests" => [ ["match", "Hello"] ], "actions" => [ ["msg", ["Hello, ", "world!"] ] ]})

みたいな感じ。

Lispで書くと

'(
  ("Context" (
    ("main" (
      ("tests" (
        ("match" "Hello")
      ))
      ("actions" (
        ("msg" ("Hello, " "world!"))
      ))
    ))
  ))
)

である。そして、YAMLにすると

Context:
  main:
    -
      tests:
        - [match, Hello]
      actions:
        - [msg, ["Hello,", "world!"]]

となる。 類似の感覚でRubyで書くと

{
  "Context" => {
    "main" => [
      {
        "tests" => [
          ["match", "Hello"]
        ],
        "actions" => [
          ["msg", ["Hello, ", "world!"]]
        ]
      }
    ]
  }
}

さらにJSONで書くと

{"Context":{"main":[{"tests":[["match","Hello"]],"actions":[["msg",["Hello, ","world!"]]]}]}}

この中ではRubyで書くのが,を忘れそうで一番危ない。 ちなみに、これに関してはPerlで書いてもRubyと全く同じになる。

入れ子が深く複雑に見えるが、作業自体は骨格ができてしまえばコピペ+改変でがんばれるので難易度自体は低い。 これを可能にするためコピペベースとなるサンプルも提供されている。

「YAMLで書く」である合理性

見ての通り、Stella Logicは連想配列, 配列, 文字列の組み合わせによって表現できる。 これらは多くのプログラミング言語に備わる基本的データ型である。

そして、そのような基本的データ型であるからこそYAMLやJSONで表すことができる。

YAMLは汎用性のあるデータフォーマットだ。

以上を以て「Stella logicは任意の言語で記述できる」が成立する。 例えばRubyなら

require 'YAML'

root = { "Context" => { "main" => [
  {
    "tests" => [
      ["match", "Hello"]
    ],
    "actions" => [
      ["msg", ["Hello, ", "world!"]]
    ]
  }
]}}

YAML.dump root, STDOUT

で良いわけだ。

専用の記述法を提供すればもっと簡単に書けるようになるが、そうなるとその記述法以外は許されなくなってしまう。 「このようにYAMLで書いてください」ということをそのまま受け取るだけであれば、もっと良い記述法が提供されるべきと思ってしまうだろうが、知識と発想さえあれば汎用性があり容易な手段で提供されることは良いことだと判断できるだろう。

ちなみに、JSONでなくYAMLである理由は、「JSONだと閉じ括弧のあとのカンマを忘れるから」である。 プログラマですら忘れるものを一般の人が意識できるわけもない。

なお、YAMLでの出力は難しい言語処理系を使う場合、JSONで出力しておき、

$ ruby -ryaml -rjson -e 'YAML.dump JSON.load(ARGF), STDOUT' logic.json

なんてワンライナーで変換できる。

だからDSL

とはいえ、YAMLで書くのは結構かったるい。 基本的なスタンスは「書きやすい方法は各々用意すべし」なのだが、それが難しく不自由を強いられるケースもあるだろう。

そのため、Stella logic builderというRubyライブラリを提供している。

このライブラリは、Stella logicを書きやすいようにするための語彙を提供する。 例えばコンテキストリーフノードmsgについて

msg("Hello, ", "world!")

のように書けるようにする。これを使うだけで前述のコードが

{ "Context" => { "main" => [
  {
    "tests" => [
      match("Hello")
    ],
    "actions" => [
      msg("Hello, ", "world!")
    ]
  }
]}}

とちょっと読みやすくなる。 もちろん、こんな使い方をするためでなく、Rubyで独自のより書きやすい書き方を制作する際の補助である。

同ライブラリには私なりの書きやすい書き方も用意されている。

c = Stella::Builder.new
c.cxt("main")
c.push(
  "tests" => [match("Hello")]
  "actions" => [msg("Hello, ", "world!")]
)
c.out

Stella::Builder#cxtはデフォルトが"main"なので別に呼ばなくて良い。

Stella::Builder#outは出力用メソッドなので、前述と同じ内容の記述であれば必要ない。 つまり、

c = Stella::Builder.new

c.push(
  "tests" => [match("Hello")]
  "actions" => [msg("Hello, ", "world!")]
)

である。すっきり。

これはRubyistにとって書きやすい設計を目指しているものであるから、「不自由を強いられる人にとって書きやすい書き方」ではない。 同ライブラリはそのような人に向けた書き方も提供している。それがStella DSLだ。

Stella DSLを用いて書くと次のようになる。

dsl

as "main" do
  on match("Hello")
  act msg("Hello," "world!")
end

finish

次のような書き方も可能。

dsl

as "main" do
  lets do
    match("Hello")
    msg("Hello, ", "world!")
  end
end

finish

あまり違いがないように見えるかもしれないが、コンテキストリーフノードが複数ある場合は大きく変わってくる。

跡形もないだろう? Stella LogicがYAMLであることが何かを縛り付けるためではなく、それぞれの技倆における自由を与えるものであることがおわかりいただけたかと思う。 もちろん、Sella DSLが正解なわけではなく、むしろ可能なのであれば最も書きやすい方法で書くことが推奨される。

Stella DSLはRuby内部DSLであるため、書き方の自由度は高く、表現上の自由度は大幅に上がっている。 Chienomiを読んでいる諸兄諸姉はプログラミングのできる人がかなり多かろうから、ここまで説明すれば十分に理解していただけていることと思う。

ちなみに、メタ記述法をとらないのであればmatch("Hello")と書く方法自体はとても簡単で

def match(str)
  ["match", str]
end

で良い。

Stella DSLはプログラムを自在に書けない人のためのStella logicの別記法であると同時に、「Stella logicはあなたの技術力によって生成するものである」という原則の純正サンプルである、ということだ。

専用記法とは

専用記法を採用するとしたら、多分こんな感じだろう。

@main

/test

match Hello

/act

msg "Hello, " world!

@でコンテキスト切り替え、/で定義する内容の宣言になっている(testで新規リーフになる)。 メソッドの引数はShell wordsとして解釈される。

他の記法と比べて文法が非常に寛容でエラーになる可能性が低い。 この記法自体は良いと考えていると、なんなら今からでも採用したいくらいだし、実際それは可能である。

だが、もしいまからこの記法を採用するとしたら、それはStella Logic(YAML)を生成するメタフォーマットになるだろう。 このルールがあるため、新たなる記法を追加するのは簡単だ。

それがもし、Stellaが受け付けるのがこのフォーマットだけだとしたらどうだろう? もちろん、それをプログラムによって生成することは不可能ではないが、そのジェネレータ、トランスレータは各々が実装しなくてはならない。 そして、これはジェネレータが生成しやすいフォーマットでもない。

YAMLという一般的なフォーマットをStellaが受け付けることによって扱いやすくなっていることが分かるだろう。

in the codes

Stella DSLは基本的に記法が異なるだけで、考え方は変わっていない。 ところが、最終的にYAMLを出力すればいいため、自由度はもっともっと高い。

例えば私が書いた「選択後特定のアクションを経て同一のコンテキストに合流する」というコードは

def sel(msgs: nil, opts: nil, params: nil)
  ccount = @ccount
  cpre = @cpre
  pname = @pname
  as("#{@cpre}#{@ccount}") {
    on finally
    
    actarg = []
    msgs.each {|i| actarg.push msg(i)} if msgs
    params.each {|i| actarg.push appendparam(pname, i)} if params

    ohash = {}
    opts[0].each_with_index {|x, i| ohash[x] = "#{cpre}#{ccount}-#{i + 1}" }
    actarg.push(sel(ohash))

    act *actarg
  }


  opts[1].each_with_index do |x, i|
    as "#{@cpre}#{@ccount}-#{i + 1}" do
      on finally
      act *x, pass("#{cpre}#{ccount + 1}")
    end
  end

  @ccount += 1
end

となっている。(Stella DSLを使っている)

余談だが、インスタンス変数をわざわざローカル変数にしているのは、StellaDSL#asObject#instance_evalを呼ぶのでインスタンス変数が読めなくなるためである。

Stella::BuilderクラスはStella::Builder#pushなどによって簡単にコンテキストリーフの追加が可能であるため、よりプログラム的に生成しやすい。

例えば実用的な意味があるかどうかは別として、学習を元に条件とアクションのペアを生成していき、トポロジカルソートによって構成する、という方法もありうるわけだ。

finally

“Stella logicはLispライクな考え方のYAMLである”

これによって得られたものは

  • 容易に変換でき、任意の言語で書くことができる
  • 任意の言語を用いて容易に異なる記法を実装することができる
  • ループや再利用、関数的コールなど本質的記述に含められない機能を生成に含めることで利用できる
  • 単純な記述でなくプログラムによって生成するといった応用が効きやすい

結局、どれほど気の利いたフォーマットや記法を提供することよりも、汎用性があり単純な形式を採用するほうがユーザーに利する。 そこまで考えて採用したわけではないのだが、そのことが改めて実感できるものであった。

Manjaro/Arch Linux, DiscordがAURから消滅 Snappy利用に …Snappy?

重要な追記

新しい記事に状況の更新が書かれている

あらまし

Manjaro Linuxの2019-08-07のツイートによれば

We are happy to announce the availability of @discordapp @ManjaroLinux. Read more about it on our forums: https://t.co/XbuhEJCFUC https://t.co/MH7Pjhn6EH

というわけで、なんかDiscordの扱いが変わったらしい。

というわけでAURを見てみると…AURからDiscordがなくなっている! Extra行きでもない!!

で、フォーラムを見てみると「Snappy使えよ」ということになっている。

Snappy?

Sanppy

SnappyについてはWikipediaに既に記事がある

Snappyとはカノニカルが設計・開発したソフトウェアデプロイメントシステムかつパッケージ管理システムであり、元々はUbuntu Phoneオペレーティングシステム用に設計・開発された。

a-ha?

ちゃんとArchwikiにも記事がある。 そして、snapcraftのサイトにManjaroについての記述がある

が、どれも後述するようにちょっと微妙である。

簡単に言えば、パッケージがライブラリとか全部含めてSquashFSにしてあるのでディストリビューションごとの調整とかいらないよ、という話である。

はっきりいってしまえば、いわゆるポータブルアプリである。 こういうの、前からちょいちょいあった。そして、いろんな意味で微妙なのである。

一番の理由は容量をえらいとる、というのがある。 管理がばらつくとか、制御しづらいとか、パッケージの信頼性が担保しにくいとか、他にも理由はたくさんある。

インストール

必要なのはcommunityパッケージのsnapd

$ sudo pacman -S snapd

snapdをenableしろと言っているが、snapdが必要なのはパッケージ操作時のみ。

$ sudo systemctl start snapd

Discordをインストール。結構長い。

$ sudo snap install discord

パッケージ操作はもうしないのでsnapdは止めてしまって良い

$ sudo systemctl stop snapd
$ sudo systemctl stop snapd.socket

起動と感触

Snappyを使って起動する。snapdは必要ない。

$ snap run discord

起動すると、フォント選択とフォントレンダリングに少し違和感があること、カーソルが異なるものになってしまうことが気になる。 あと、Cinnamon上でアプリケーションアイコンが表示されない(systrayには表示される。これは.desktopファイルの設定によらず改善しない)。

また、アプリケーションアイコンのコンテキストメニュー(通称デスクトップメニュー)に機能しない “New Window” がある。

使えないわけではないけど、やっぱりいまいちだ…

(2019-08-12 追記)

システムを再起動したところ

  • アイコンは出るようになった
  • discordコマンドで実行できるようになっている (/var/lib/snapd/snap/bin/discordを呼んでいる)
  • カーソルとフォントは相変わらず
  • .desktopメニューに“New Window”があるのは相変わらず。[Desktop Action]は定義されてないけれど
  • systrayのDiscordアイコンクリック時に“Top Secret Control Panel”が出る。これは右クリックの挙動で、クリック時はwindow activateされるのが正しいはず

Snappyについて

Manjaroの場合AURにDiscordがあったので、それを前提にするのであれば大幅な後退であるというのが基本的な印象だ。

しかし、それはちょっと傲慢過ぎる。

Arch LinuxやManjaro Linuxの場合、ほとんどのソフトウェアはAURにあり、そのいずれも(Launchpadと比べても)クオリティが高く調整が行き届いている。 だが、現実には依然としてバイナリのみの配布となっていて、Ubuntu向けdebパッケージのみが配布されているケースはかなり多い。Discordもそうである。

このようなソフトウェアの存在のために現実的にはUbuntuパッケージが使えるUbuntu系列のディストリビューション、AURで調整されているArch系列のディストリビューションぐらいしか選べないという状況が出来上がる。せいぜいがFedora。 実際にopenSUSE, Mageia, Sabayon Linux, PCLinuxOSを使っていたときは使えるソフトウェアに著しい制限がかかる状況であった。

そうした状況は健全とはいい難いため、そのようななんらかのポリシーによって限定的なパッケージでバイナリ配布されるソフトウェア1が広くディストリビューションを限定せずに利用できる、という意味ではSnappyによってサポートされるというのは悪い話ではない。

だが、Snappyに統一されるべきかというと、ちょっと首肯は難しい。 Snapcraftがどれだけメンテナンスされるか、きちんとチェックされるかというところで、やはり信頼感はかなり差がある。Launchpadが結構ひどく、AURがほとんどのパッケージはちゃんとしていることを考えるとやはりAURで欲しい。クオリティ的にもSnappyがいまいちであることを考えるとSnappyを理由にAURからドロップするのは結構残念な気持ちになる。

余談だが、Ubuntu向けdebパッケージで提供されるっていうのは結構扱いにくい。debパッケージはちょっと特殊なので、PKGBUILDを書いてリアッセンブルするのが割と手間なのだ。specを変換するだけのRPMのほうがずっと楽だろう…と私は思うのだが、なぜかAURのパッケージ群はRPMがあるソフトウェアでも大抵debをリアッセンブルしているので、ひょっとしたらこれは私の認識が正しくないのかもしれない。


  1. 「バイナリ配布だから限定的なパッケージである」という理解は間違っている。例えばFirefoxはLinux向け完パケバイナリが存在し、ポータブルアプリとして実行可能である。↩︎

Inflaton Stella 〜チャットAIの理想への挑戦

今回は弊社で展開するチャットボットAIサービス “Stella” に関するお話。

この記事はStellaの側から見た話ではなく、私の側から見た話になる。 つまり、Stellaユーザーに向けた記事ではなく、私をウォッチしている人に向けた記事である。

また、この記事中では各ソフトウェアを「AI」と読んだり呼ばなかったりする。 これについて私の立場を明らかにすれば、私は「AIとは自己更新するプログラムのことである」と定義しており、これに照らし合わせればStellaも、Erinaも、Surtrもあくまでただのプログラムであり、AIではない。 そして、今の世の中にAIと呼ぶに値するプログラムはない。

実際にはシンギュラリティを狙うSurtrは部分的に自己更新を行うが、何に対してどのように更新を行うかというのは規定されている。 例えばマニュピレーションモジュールをSurtr自身が生成することはあるが、SurtrのコアプログラムをSurtr自身が変更することはない。 このため、SurtrはAIと呼ぶに値しない。

Erinaに関してはそもそもSurtrにおいて自己更新の対象となっている機能を一切使わないため、Erinaには自己更新機能がない。 そしてプログラム的にも、プログラムを変更する機能は除去されている。

Stellaに至ってはもっと単純にプログラムである。

これらを「AI」と呼ぶのは、どちらかというと商業的側面が強い。 だから、Chienomi的には便宜上そう呼ぶことはあっても、「AIについて」語ろうというスタンスではない。

もちろん、ディープラーニングごとき1がAIだなどとは微塵も思っていない。

コンセプト立案

Stellaのコンセプトに関しては諸々述べているが、中心になっているのは

「シンプルに、実用的に」 (=Erinaとは逆)

である。

Erinaの場合、あれは研究の副産物であり、Surtrの一部である。 だから、Erinaの場合コミュニケーションボットとしては実用に供することはないという前提があり(そもそもその成果物をリリースすることは不可能なのだから)、その複雑さは実利とは全く関係のないところで不可欠なものである。

Erinaはシミュレータだから、過程の保証が出来なければ価値がないので、結果が同一であるということには意味がない。

また、Erinaの名前が「ELIZAの逆であり、ELIZAに直交する」ということを意味してつけられていることからもわかるように、「反ELIZA的」なAIデザインである。 ErinaはSurtrにない固有コンポーネントも持っていて、これはコミュニケーションに特化したものなわけだが、これはもう前提として「ELIZAは正しくない」という考えがある。

Stellaは全く逆に、「ELIZAは実用的だ」という観点から構築される「ELIZA的」なチャットボットである。

このようになる根本としては「人は基本的に人の話を聞いていない」ということにある。 私は記憶能力の構成上、かなり相手の発言を正確に聞いているのだが、大抵の人は「2つ前の言葉の反復」を求めてもまず言えない。

ここらへんは私の例によって悪魔的な私の実験によって確認されている。 これは単純な例なので手のうちを完全に明かしてしまうが、Erina最終調整の段階で多くの人と対話を試みるチャンスがあったわけで、ここで証明した。 手法としては、「メンタリズムに長ける」という情報を入れた上で、そこに関心を引いてから誘導的に会話を進め、メモを取り出して一切の説明なしに会話を継続する。そして「2つ前にメモを見ながら私はなんと言ったか?正確に言ってみて」と言うわけである。 実際にメモを読み上げただけなのだが、これが「メモを読み上げた」という印象に残る行為を伴っていても当たらない。 もちろん、この作業には「メモの読み上げが会話の文脈上自然な内容にする」という小技も入っているが。

そして、ある会話の流れで「実際の内容はとても変なことを言っているのだが、非常に高い確率で全く違う内容に理解する」という会話を展開するテクニックなんかも身につく。ちなみに、これによって相手が私の発言を全く違うように捉えさせると、実際に私が何を言ったかということを反復させると実際に言ったことからは「凄まじくかけ離れた」内容になる。

これはほんのいち要素にすぎないのだが、重要なのは「人の脳は解像度が低く、普通は収束する」ということである。

だから会話すること自体は緻密に書かなくてもなんとなくそれっぽく返せていれば会話は通じているように見える。 Erinaが「ごまかし方」にこだわっているのはそれが曖昧さをつなぐ「糊」になるからだ。

そして、人の行動パターン、言葉は想像よりもずっと種類が少ない。 いや、正確には全パターンを網羅しようとすると実際に膨大になるのだが、「例外的な小数を除外すると極めて少なくなる」という特性がある。

「自然言語の対話の7割は単純な正規表現マッチングでカバー可能である」というErinaの研究結果の副産物がここで生きてくる。

まして実用的、しかも企業におけるサポートの一環としてのチャットであればなおさら絞り込める。 「ちょっと気の利いたテーブルAI」でも十分実用的に扱えるのだ。

こうしたことを踏まえて実用的なチャットボットを定義する。 実際、私は世の中のサポートチャットボットが実用的だとは微塵も思えない。

じゃあ実用的なチャットボットってなんだろう? チャットボットを求めるときというのは基本的に「help needed」なのである。 このときの私の心理としては

  • 都合を問わない即時性。 営業時間まで待とうとも、営業時間に問い合わせようとも思わない
  • 人とは話したくない。電話は大嫌いだし、メールもそんなに好きではない。問い合わせフォームはメールよりも嫌だ。対人につなぐチャットボットとか最低すぎる
  • 対人チャットはそこまで嫌ではないが、なんらかのトラブルやミスを恐れることになるので精神的には嬉しくない
  • 期待値は割と低い。だいたいサポートなんて役に立たないものだから、解決する可能性は5割あれば上等だ
  • チャットボットは最も敷居が低い問い合わせである。最初に試みて、解決しなければ人手を煩わせることも考えるかもしれないし、諦めるかもしれない

である。じゃあ、help neededな状況でこういう心理に対して欲しいものはなんだろうか。 答えは簡単。FAQである。

でも、私はFAQってめっちゃ見る人なのだが、ほとんどの場合FAQって全く役に立たない。 まぁ、ナレッジサービスやフォーラムでも既存の質問がない問題にぶち当たってサポートを頼ることが大抵だし、自己解決力が高いとそうなってしまうのでFAQが役に立たないのは必然ではあるのだが、それにしても「これはあっていいだろう」と思うものがFAQにないことって本当に多い。 多分、実際の問い合わせをちゃんとFAQに反映していないのだ。

じゃあFAQが欲しいというのは机上の空論なのではないか、という話をすると、そんなことは全然ない。 Googleもまるで役に立たない今、私はhelp neededな状況を非常に高い確率で解決してくれるものを知っている。Archwikiだ。

つまり、wikiのような知識集合は問題を解決する。 ユーザーが本当に必要としているのは「ちゃんとドキュメントを作ること」と「検索エンジンを作ること」である。

だがしかし、現実にはこれは効果を発揮しない。なぜならば

  • 人はFAQを読まない
  • 人はドキュメントを読まない
  • 人はGoogle以外で検索しない

からだ。

まぁ、私の気持ちとしては「人ってそんなにチャットボット使うかなぁ…みんなあんまり使わない気がするしアクセスされないのでは」という気持ちはとってもたくさんあるのだけど、そこは弊社取締役の力説を信じることとする。

まぁ、仮に人はFAQは読まないがチャットボットは利用するのであれば、チャットボットにFAQの代わりをさせてあげればよろしい。 ちゃんとドキュメントを整備しているのであれば、検索エンジンの代わりをさせてあげればよろしい。

さぁ、「実用的」は定義できた。 Erinaは経緯的にも目的上も複雑にならざるをえなかったが、その意味でも実用に耐えず、プロダクトとしては話にならない。 「問題を解きほぐしてシンプルにすることでシンプルなソリューションを生み出す」ことが私の真骨頂。simplify, simplicityは私の生き方に反して私が力を行使する上で重要なキーワードだ(おかげでcomplex simplicityというパワーワードも誕生してしまった)。 ここまで明確かつシンプルな定義ができたのだから、今までの経験から必要なものをessentialに抜き出せば要素は少なくて済む。これをできるだけシンプルに組み上げよう、ということがコンセプトとして立てられた。

背景は複雑だが、プログラムとしての設計、挙動、ルールはシンプルに、ユーザーにとって理解しやすく、ユーザーの作業も単純に。 これでコンセプトは確立された。

ここで鬱陶しくも自己アピールをしておくのであれば、この節に書かれていることは熟考と検証の末に導き出したような内容になっているが、実際にはこの節の内容を考えるのに10秒とかかっていない。 確かに中学生、高校生のときと比べると頭の回転は桁違いに遅くなり、それによって人を恐れおののかせるようなものではなくなったとはいえ、研究によって積み重ねたものは血肉となっており、この程度の内容であれば10秒もあれば検討は終わる。

諸君、これが研鑽というものなのだよ!

もちろん、私が研鑽の至らない分野ではこうして瞬殺ボッコボコにされるのである。

if文AI

さて、if文AIという言葉を耳にしたことはあるだろうか。

この言葉、実は割と古く使われていたのだが、フィーチャーされるようになったのはAIブーム以降である。 「AI=ディープラーニング」の図式が出来てから、技術的に安易な「ひたすらifで条件式とのマッチングを行って挙動を決定する」という手法(あるいは具体的にその手法ではなくともそのような単純な手法)を用いたAIに対する揶揄として使われるようになった。

この揶揄は、「ディープラーニングこそが正義でありAI、ディープラーニングにあらずんばAIにあらず」という安直な思想に基づいている。 だからそれ自体は割と取るに足らないことではある。

しかし考え見て欲しい。 「条件が明らかである」=「問題が明示的である」という状況下では条件を判定し、それに対して最も適切な挙動を明記したプログラムを動かす以上に適切なことがあるだろうか? 世の中割と「それプログラムで解けるじゃない」ということをディープラーニング使いたがる状況がある。

だが、問題が明確なのであればそれを解くプログラムを書くのがプログラマではなかろうか。 それは競技プログラミングと同じようなものであり、問題が定義されているのであればその解を 書かなければならならい のである。

プログラムというのは本懐を遂げるのであれば次のいずれかだと考えられる

  1. 明確な問題を解く
  2. 特定の用途に供する
  3. 不明な用途に対して明確な意図を以て機能を提供する

1は競技プログラミングなどでよくあるもの、2はアプリケーションプログラムのこと、3は例えばgrepやsedやlessなんかのことだ。

そして、問題が明らかなのであり、それを解くことを意図するならばそれを解くプログラムより優れたものなど存在しない。

AIが必要とされるのは問題が明らかでなく流動的であり、導出条件も不明であることから「なんとなく」解くという妥協を行う場合である。 AIが普通のプログラムより優れた解を出すと思っているのなら、さすがにそれは無知にすぎる。 普通のプログラムが解けない曖昧な問題に対し完璧でなくてもいいから答えを出すのがAIである。

そして、「問題と導出条件が不明」であればプログラム自体が動的でなければならず、その機能を正しく果たすにはそのプログラム自身が動的でならない、と私は考えるのだ。

さて、もしも条件を事前に用意できるのであればif文AI、というか問題を解くプログラムは最も正しいということを述べたわけだが、ここからはSurtrとErinaの挙動に触れていこう。

Surtrの場合「自分が取るべきアクションをプログラムとして生成する」という手法を取っている。 これは、私の当時の技術力不足を補い、できるだけ単純化するためにとられた手法である。

一般的にプログラムはその挙動の過程で問題は収束していく。 ところが、不明な問題に対するプログラムを書くと、どうしてもその処理中に問題は拡散する。 だから、「実行ステップ数を事前に予測する方法がない」という非常に特殊なプログラムになる。

単純に再帰すれば良い、とも考えられるのだが、問題が不明なので処理を決定する段階で深さ優先探索ができない。 だから各ステップごとに次のステップで実行するコードをキューイングしていくというような感じになるのだが、これがうまくできなかった。どうしてもGOTO文を使う以外の手法が思いつかなかったのだ。 もちろん、今であれば関数オブジェクトを配列にpushしていくみたいな方法を考えるだろう。

ただ、その処理自体が実に様々な言語で書かれた様々なプログラムに分散しているので、外部に出さないことにはあまりメリットがない。 だから、挙動としては無限ループの中でステップ処理を行い、次のステップをプログラムとして出力する。このプログラムは最終的な段階ではZshスクリプト、またはRuby DSLスクリプトである。 そして、これを実行し、実行結果を次のステップに反映させる。

Erinaにも同様の機能は使われているが、Erinaの場合なるべく関数オブジェクトの配列(というかRuby的に言うと大部分はメソッドオブジェクトの配列)で処理するように変更されている。

この挙動は「可能な限り生成を遅延した上で、プログラム自身によって書かれたif文AIを実行する」みたいなものである。 かなり際どい技法だが、Surtrの中でもこれは応用が効くものだと思っている。

Stellaはこれをもっともっと単純化したものであり、言ってみれば「if文AIを書くためのフレームワーク」である。

「なぁんだ」と思うかもしれないが、我々が肝に銘じておかなくてはならないことがひとつある。 それは、「顧客が望んでいるのは技術的に高度であったり、技術的トレンドを用いて作られたプログラムではなく、問題を適切に解決するプログラムである」ということだ。

確かにStellaはプログラム自体は至って単純だ。 なんといっても、本体コンポーネントのみに関していえば、0.0.1ではわずか170行に収まり、0.0.16にあっても428行に過ぎない。2

しかし、私はそもそもプログラムの記述で勝負するタイプではない。 数学的素養がなく、アルゴリズムも得意とは言えない。もちろん、それなりに経験は積み、ある程度の記述力はあるが、突出して優れているとはいい難い。 私の勝負どころは発想と問題の整理であり、「設計」を至上とするタイプなのである。

「Stellaをif文AIを生成するフレームワークにする」という判断自体が、その技倆の一部なのである。 そして、そのフレームワークで提供される機能の選択によって「実用的なif文AIを構築する」ことへと誘導するのである。

巧みな設計で勝負するステラ

プログラミングに関する素養がなくても、また私の固有能力であるコミュニケーションフローコントロールを持っていなくても実用的に書けるようにする、というのもひとつではあるのだが、実際にはステラはこのような設計になっているために単純なif文AIよりもはるかに複雑なつくりが可能である。

その挙動を決定する「ロジックファイル」はYAML形式になっている。 一応は直接に記述することを想定してはいるものの、その階層的複雑さを考えれば何らかの手段で生成するほうが望ましい。

YAMLという汎用性のある形式を取っているために「お好きなプログラミング言語でお好きなジェネレーターとDSLを書いてお作りになれます」なのだ。

実際、純正でStella DSLというRuby DSLが提供されているが、別にそれを使わなければならないというわけではない。

そして、プログラムによってロジックファイルが生成できる、ということは「if文AIを動的に生成できる」ということにほかならない。 基本的には「事前に静的に生成される必要がある」と説明してはいるが、現実にはそのような制約が存在しているわけではなく、インスタンス全体共通にはなってしまうものの、任意のタイミングで適用可能である。 インスタンスの制約はあくまでサービス上のおはなしなので、純粋にStellaの能力を語るのであれば、会話中の動的な変更だって可能だ。

そう、つまり「任意のタイミングでif文AIを動的に構築可能」なのであり、これは「次の挙動をプログラムで生成する」というSurtrと同じものである。

ここで言っているのは潜在能力の話であり、実際には「現実性」という形でもっと制約されている。 だが、これは「制約しないことが正しい」わけではない。だってStellaは実用的なチャットボットであり、これを構築するのは非エンジニアである。「チュートリアルに従って自然に書けば実用的に動作する」のが望ましいのであり、過剰に複雑で高度な機能を解放することは誰も幸せにしない。

それはさておき、if文AIというのは単純で幼稚という揶揄に使われる言葉となっているが、「任意かつ動的にif文AIを構築する」というのは言うほど単純ではない。 概要として、実作業としてのシンプルさに反して「それが正しい」と判断しているモデル自体は極めて高度である。 その可能性のほとんどは使わない前提になっているが、使うこと自体は任意であり、発想力と技術力のある者にはその選択肢は常に開放されている。

さらにいえば、StellaはコンテキスチュアルなAIである。 これは私が世のEQ AIに対して抱いている強烈な不満であり、「文脈を一切考慮しない」のはEQ AIとして決して許されざることだと思っている。 だからこそErinaは文脈処理に対して極めて複雑なプログラムが組まれている。

動的に文脈を判定してしまうとさすがにとてもユーザーに制御できるものではなくなってしまうので、Stellaではユーザーが明示的に使う形にしている。 これもロジック自体を動的生成する構造を選択すれば動的に判定できるのだけど、そこに言及するとサービスとしてのStellaからはかけ離れてしまうのでおいておこう。

コンテキスチュアルであるという特徴は他に類をみないので、「if文AIが動的に生成される」という要素を「実際にはしないほうが普通だから」という理由で除外したとして、じゃあ「ただのif文AIじゃないか、稚拙なもので特に強みはない」みたいな話になるかというと、「コンテキスト機能があるからね。コンテキストを踏まえたフローコントロールを行うif文AIを書くのは全く単純じゃないと思うよ」という返しができる。 「ロジックファイル」という中間レイヤーを介することでプログラムとして書くと非常に煩雑になるフローがいともたやすく構築できてしまう。

それがフレームワークというものだろう?

ここまで話せばわかるかと思うが、実のところStellaはErinaとは全く異なる手法、設計、構造になっているにも関わらず、潜在的にはessential Erinaである。 途方もなく複雑で膨大で動的なErinaから「実用的な意味で本当に必要なもの」だけを残した不自由を強制することで全く違う性格のプログラムにしている。 StellaをベースにしてErinaのようなプログラムを構成することも可能であり、Stellaサービスとしてもその余地は残されている。そして、StellaベースのErinaを作れば、現在のErinaよりも設計が改善する可能性すらある。

だが、それはあくまでプログラム的な話である。 StellaはInflatonのサービスだから、これらはあくまでプログラムとして、技術として、裏側を覗いた話であり、利用者に対して主張することではない。 なのだが、そうしたバックグラウンドがなければStellaは生まれないのである。 こういうものは膨大な知識と蓄積の上澄みなのだ。

ステラの可能性の範疇

前述のようにStellaの理想的な用法はFAQ、あるいはwikiへの導線である。 だからStella自身を複雑化することは全く推奨されていない。

だが、どこまでできるのかという話をするとまるで変わってくる。 なんといってもStellaは外部連携が可能であり、入力時に割り込んでStellaをバックエンドとして使用できる、挙動は動的に生成できる、実行中に別のサーバーに対するhookを使ってコールバックを実装可能であるということから実質なんでもできるし、 ディープラーニングとも排他的ではない!!!

このあたりは「かける労力次第」であり、どのような規模のロジックを書くか、どのような規模のシステムと連携するかによっていかようにもという話となる。 だから構築規模・コストとも青天井であり、基本的に多くを望むべきではない。

それでも、労力さえかければ可能性は無限大だ。

それが行使される計画がある。Stella Flagship Chatである。 これはStellaを使ってEQ AIを作ろうという計画だ。

労力もコストも本当に膨大だし、私の手があいている限りで行われることになるから、まだpendingになっているが、 Erinaのような完璧なレベルではなくてもそれなりに会話が楽しめるレベルにはなるはずだ。

そして、それが可能だというのは、「大部分は集約可能である」という経験に基づいている。 自由文脈では発端を推測できないため本当に自然に会話できるようにするのは現実的には不可能に近いが、少しずつ形になっていくだろう。 どこまでがんばれるか不安はあるが、うまくいかないとすればそれはStellaの限界ではなく、私の労力上の限界である。 (そしてそれは、決して低い可能性ではない)

それは私のプログラムだから

一般的に言えばイージーなプログラムを書く私だが、私としては制作するプログラムには著しいプライドがある。

例えば誰かの悪意によって行使されるプログラムを書かない。 誰かを侵害することを目的としたプログラムを書かない。

そんな強いプライドから、「汎用性のあるプログラムはあくまで道具である」というものまで。

そう、私が書くプログラムは、単一の問題を解決するためのものを除けば汎用性を前提としている。 プログラム自体がどのように使うか、なんのために使うか、どのような利点があるかということを 規定しない

商業的にはそれは嫌がられるんだなぁ…というのは感じるのだけど、そしてそれは社長として正しいことではないのかもしれないけれど、 それでもそれはプログラムに課すべきことではないと思うのだ。

サービスとしては見せ方を工夫すれば良い。実際、プログラムの汎用性に対して意図に反してもっと明確なイメージをもたれているものなんでいくらでもあるのだ。 だから、私としてはInflatonとして販売のためにStellaをどのように形容しようが、何を特色としようが、何をウリにしようが、間違っていない限り構わないし、それはソフトウェアプロモーションにおいては私の責任だが、それを販売の形にするのは私の仕事ではない。 だから、プログラム自体は中庸中立であり、私はそれを様々な切り口から解説するのである。

おわりに

Stella関係の話は公式ドキュメントとかでも結構色々書いてはいるのだけど、当然ながらInflatonのお客様はChienomiの読者みたいにエッジな人たちではないし、私としてもよそゆきのお話だからめちゃくちゃ気を遣うから書きたいことが全然書けなかったりする。 その意味で、今回こうして書いてようやっとスッキリしたという感じである。

Stellaはコード上の魅力は非常に乏しい――いや、ちゃんと言うならば割と私の今までの経験を色々活かしていて、動的メソッド呼び出しをしていて非常にシンプルにかけていたりとか、どの時点で正当性を保証し除染するかとか、メタプログラミングしていたりとか割と一般的でないテクニックが駆使されていてそれはそれで面白いかもしれないが、別にそれが必須なわけでもなく、力技で実装もできるものだから、その意味で「どうやっているのかわからないことをやっている」とか「高度なアルゴリズムを駆使している」みたいな要素はない。

だが特にそのデザインは他の誰も持っていない知識や背景からきている。 派手な魅力やアピールではなく実直に。 私が仕事としてプログラムを書くようになってから心がけていることを形にしたものだし、Plutoがかなり不本意なプログラムになってしまったことを考えると、これは私の、誰にも言わなかった日々を含めての集大成だとも言える。 これほど人より知悉している分野もないし、ちょっとやそっとの研鑽でこれを越えるプログラムを設計することはできないだろう。

だから、本当にこのプログラムに「ステラ」の名を、つまり原初宇宙の名を関するInflatonとしてその果てである「星」の名を冠してよかったと思う。 アイディアとしては「デネブ」などの女神星の名を関する案もあった。だが、そうしていたら今ほど輝きを感じることはできなかっただろう。

当記事で書いたようなStellaの魅力やよさは、直接にはユーザーが感じることはない。 ユーザーが感じるのはその結果の部分だけであり、そしてそこだけを見れば流行りのディープラーニングAIなどと比べればしょぼくて、みすぼらしくて、とてもとても見劣りする古くて単純な技術のソフトウェアでしかないのだ。

だが、私は確信できる。 使ってもらえばわかるはずだ。 100%でなくても、このソフトウェアは解決すべき問題を、着実に解決する方法を提供する。 そして、その積み重ねは100%へと近づくことを意味する。 それはきっと必要とするものであり、私が、私達が追い求めるべきは見栄えや華やかさではなく、正しく必要とするものなのである。

そして、このことに関しては――人心と人動、コミュニケーション様態に関すること、ましてその中で最も多くのデータを占めるチャットに関することであれば――世界中、誰にも負けないという自負がある。一番最初には、私が5歳のときに抱いた疑問から始まり、ずっと考えていて、今に至るまで積み重ねてきた。

ディープラーニングの信奉者よ、さぁ、勝負しようか。 よりよいエクスペリエンスを提供するチャットAIはどちらか。

私には、自信がある。


  1. 勘違いしてほしくないのだが、ディープラーニングを見下すつもりはない。私は限定的で使いどころが難しくコストの高い技術だと思っている。技術的には高度なのは認めているし、その高度さが適切さを妨げているとも思っている。ブロックチェーンと似たような話だとも思うが、あっちは技術的にもっと趣深くてテクノロジー好きの好物だと感じる。私もそうだし。↩︎

  2. 「過ぎない」とか言っているが、428行というとずいぶん拡張されたし、本体だけで428行、ステラコンポーネント全体だと1120行あるので個人活動では到底書かない規模に到達している。ちなみに、ドキュメントも含めるとユーティリティ分は除いて4831行ある。↩︎

Erinaの最終調整のときの話

Erinaにはfascinateというデータベースがある。 これはペルソナというErinaの最終調整によって作られたデータベースである。

手入力によって作られたデータベースというのはいくつかあるが、このfascinateデータベースに関しては完全にErina専用で、「Erinaを人間らしく見せるための味付け」になっている。

そして、その特性が他とは異なるだけでなく、作られ方も全く違った。このデータベースだけが至って主観的な作りなのだ。

この調整を行わないとどうなるか。 問題は2点ある。

ひとつは、意味難解な回答を返すことがある。よくよく考えればわかるし、間違ってはいないのだが、あまり人間的でないというか、 天才が過程をふっとばして回答したようなつながり方になる。しかも、元発言の意味には対応しているが趣旨には対応していないので、適切ではない。

もうひとつは、おかしな回答をして、しかもループしやすい。 これはサンプルになっているデータにおける「会話にならないおかしな回答」の割合が非常に高い、つまりS/N比が低いからである。 最近はこれで壊滅的になってしまっているので、取得したデータを捨てる作業に追われたりもした。

このことから、恣意的に特定の会話パターンに誘引するようにしてこうした問題を軽減している。 意図としては「会話を続けるのが上手な人の会話パターンに誘導しようとしているのである。

この問題は、一般的なディープラーニングと同じ問題に陥っているということができる。 「多い≠良い」であり、「良いを判断するのは極めて難しい」という問題を、Erinaは攻略できないのである。 そこでそれを人力で誘導しているわけだ。

前提

そもそも、Erinaは完全に、私の特異性に基づいて成り立っている。 だから、これが前提として理解できていなければこの話は全く理解できないし、同時にこの手法は誰にでもできるものではない。

私には多くの欠陥と、特異性がある。 欠陥と特異性の間に相関があるのかは不明だが、基本的には欠陥を可能な限り特異性による能力で補う状況になっている。

記憶と認識に関するものの特性は大きい。

まず、私は単純な記憶がほとんど機能しない。 単純記憶が弱い、と言ってもいいのだが、正しくは島状記憶を維持する能力がない。 だが、連想記憶に関しては容量的にも人よりむしろ優れているくらいだし、正確性はかなり高い。

無理をすれば、結構な長時間に及ぶ出来事を、認識できなかったことを含めて覚えていられる。 これは尋常ではないほどの負担がかかるが。

そして、認識に解釈を伴わないというのも非常に特異なところだ。

一般的には、人は自分が理解しうる概念の元でのみ物事を認識し、記憶することができる。 だから、人は見たもの、聞いたことを直後でさえ正確に述べることは非常に難しい。 これは、心理術では非常に重要な要素である。見たものを思い描くとき、人は映像を思い浮かべることができるが、「正しくない映像を」思い浮かべる。聞いたことを思い返すとき、直後であるにも関わらず内容がまるで違うということはしょっちゅうある(自分が言ったことを正確に繰り返せない人もとても多い)が、この欠落する情報を使って刷り込みを行える…などだ。

これは人によって程度がかなり異なるが、無変換で維持するのは実のところ障害とされている。 程度が異なるのは、どちらかといえば概念をどれだけ細かく作れるか、にかかっているのだろう。

私の場合は実のところ無変換ではない…のだが、あるがままの状態を概念として定義できるので、事実上無変換と変わらない結果を得られる。 これだと私の脳がもたないので、 手動で 内容を整理して必要な要素だけを残して切り捨てていかないと頭がパンクしてしまう。突然意識を失うのは結構危ないので、必須の作業だ。

また、人は言語化されない概念を捉えておくことを困難とするが、私は概念に言語を必要としない。 以上のことから、普通は類似した、あるいは近似したものはどんどん集約されてしまうから結果として事実を事実として認識できない状態が発生するのだが、私の場合は集約されるべき余地がないためそのまま認識しているという状態が発生する。もちろん、これは 認識できれば の話であるが。自衛のためにも普通はフィルタをかけている。あまり大きな声で話されたりすると一字一句覚えてしまうから辛い。

そして、並列性の高さだ。 私は話しながら書き物をすることもできるし、読みながら話すことも一応できる。多重出力と比べて多重入力はちょっと苦手だが。 それでも、おしゃべりしながら別の人のおしゃべりを聴くようなことは普通にできる。もちろん負担は大きいができる。そして、前述の通りそれを全て覚えてしまうこともできる。

これらの特殊な認識方法、分解方法、そして記憶方法がErinaの設計に至る発想にも繋がっているし、当然ながらデータもそういう認識の世界のものになっている。 つまり言語化されるような定義された概念ではなく、そこにあるものをそのまま写し取って切り刻んでぶちこんだものなのだ。

余談ではあるが、このままだと全く出力できないので、私も会話するときなどはちゃんと変換する。 だが、変換すると自動的に認識したものは変換された(言語化された)ものに置き換えられてしまうので、後述する最終作業はとっても脳に負担のかかるものだった。

経路、類型、感情と思考の形成

ErinaがConnexonでどのように情報を扱っているか、という話はまだどこでもしていないけれども、そのパラメータは常人には決して理解できないものになっている。 実際はもっと細かく分かれるのだが、簡単に言えば発言を一字一句含めた状態でハッシュ化したようなものだと思えばいいだろう。ほかのわずかな違いでも異なる値になるようになっており、どの言葉からどの言葉へとつながったか、ということを記録している。 実際には、somaはsoma同士の距離がわかるようになっており、言葉的には近いsomaというのは判別できる。だが、これを同じ意味だからとまとめたりはしないし、どういう意味のことを言っているか、なぜそう繋がったかということを判断したりはしない。

感情も、喜怒哀楽などという4種類ではない。私ですら多分、千通り以上は感情の類型を認識しているから、Erinaは何億もの感情の種類を知っていたとしても不思議ではない。

接続理由というのはErinaにとっては非常に重要なので、接続するのは接続しうるもの全てなのだけど、それを接続しうる理由もちゃんと入れるようになっている。 このとき、理由を断定したりはしない。ありうるものを網かけするようになっている。

あるsomaから別のsomaに接続されるとき、それが接続される理由は一意ではない。 理由自体は複合的かもしれない。もっとも、Erinaは複合的な理由は複合しているのではなくそういう状態であると量的に判断するけれど。

少し脱線するけれど、この話はちょっとおもしろい要素があって、「感情と理由」に関しては一次元的動きである可能性があったりする。 つまり、その状態を数値化したときに、soma同士の接続が動機の値がいくつからいくつのときに発生しうる、というような観測が可能であるように見えるのだ。 ただし、これは連続的可変の並び順という問題があって、とてもではないけれど私にできるようなシロモノではないけれど、そういう特性があるように見えるという話。

で、話を戻して、今の所一次元的に扱えていないので、考えうる状態を、ちょうどビット論理和のように扱う。 これを反転マスクとして、この範疇にないものは現在の観測上この経路を持たないものであるとみなす。

そして、単独の接続ではなく連続の接続から、「こういうルートでこれ的なsomaを辿っていくパターン」というのを覚えていく。

人は驚くほど判で押したようなやりとりばかりしているので、実は大抵の場合決まったルートで決まったsomaを辿る。 このとき、推測される同じ理由で経由される別のsomaというのが登場する。これは、言葉は違っても結局のところ考えてること、やっていることは同じ、ということで中身は同じものとして扱える。同じルートを辿っていても理由が違うと同一には扱えない。

ここらへんの機能は、判定に使うものが自分が判定することによって情報になるものに依存している構造なので、成立させるまでが果てしなく長かった。 orbital designの採用理由でもある。

さて、同じ動機で辿る場合、あくまで表現の違いというか、見かけ上の問題に過ぎないため、単にバリエーション程度に考えることができる。 ある程度同じ動機で同じルートを辿って、途中から今までなかった方向へ分岐していくことはまずないため、これが行動予測に使える。 これは、心理術でも同じである。

手動入力ではErinaに直接会話のつながりを与えていく。 このとき、まずErinaは盤面を作ってから接続していくので、Erinaに盤面を作成するためのヒントを与えなくてはいけない。 これは、つながりから自動的に推測してもらうこともできるし、普通はそうしているのだが、それだと手動入力する意味がない。

Erinaに状況を与えると、Erinaは「似ている」と感じるsomaを提示してくる。 これに対して「どれくらい近似しているか」を回答する。この「どれくらい近似しているか」は曖昧すぎて、細かく回答しようとしてもあまり精度が出ないので、7段階である。

次に接続できる理由をたずねてくる。 こっちは結構きつい。512ビットハッシュみたいなものと向き合うことになるからだ。 ただ、これはこれでその理由で接続された他のsomaを参照することができるので、同じ心理状態や動機に見えるかどうかを回答することになる。こちらはシンプルに3段階。Erinaが推測する理由全部に答えることで、「この理由であればこのような心理状態になってこのような行動を取ることができる」というふうに認識する。

手動入力したものは、Erinaが積極的にそのような振る舞う理由になる。 意味付けや重み付けに関しては、また複雑なアルゴリズムなのだけど、Erinaは判で押した会話は価値が低いがよくあると考え、価値の低い会話で応答に使うし、意味ある会話をされていると判定すれば意味ある会話における適切性を元に応答する。 手動入力する場合、意味ある会話である、という認識を強制することができる。

ちなみに、実際にはある程度一般化できるようになっており、かなり強引に「得意な展開方法」に集約する。

作業

作業そのものを抽出すれば、「話して会話の経過を記憶する → ひたすら入力する」である。

この会話において重要な点は2点ある。

ひとつは、私の発言である。 私は自然な会話において「同じ会話に誘導しようとする」。 これは綺麗なモデルなら「NステップでXに到達する」というのがあったりするのだが、自然な会話では相手はもっと無軌道に話すので、それをやってしまうと参考にならない。

ルールは次の通りだ。

  • 固定の話題振りを用意する
  • 必ず相手の発言を踏まえて(相手の発言要素に回答する形で)回答していき、用意した話題振りに到達する

基本的にこの話題振りは(振りやすいように、またその後のデータがとりやすいように)「〇〇ということがあった」というものである。

さらに、この話題振りには「この話題において私が話したいこと」というのを用意しておく。 これは、語りであれば(つまり、講演であったり、配信動画であったり、ラジオであったりすれば)その話題振りをきっかけに盛り上げたり落としたりしながら盛り込める内容である。だが、純粋に会話で出すには、私が話したがる場合を除けばこれらは話題振りができたとしても話すチャンスがない。 これを話すためには相手が

  • 会話の流れで私が追加で話すチャンスを与える
  • 話に続く余地がある点を掘り下げる

のどちらかをしなければ登場しない。

そして、「対象に含まれるうちのなるべく多くの人に対して、同一条件で会話する」のである。

ここで採取するのは、私の会話においては、どのような技法で同一のポイントに到達できるか、である。 私の会話技術向上にもなるが、相手の発言を無視することなく、話題を切り替えることもなく自然に到達しなければならないので、いかに違和感なく話をスライドさせるかが重要になる。これは、Erinaにとって会話をスライドさせる方法と、会話をスライドできる跳躍量の具体的サンプルになる。

そして、後者(相手側)は「上手な会話」を採取することである。 Erinaとしては探り合いの会話の中で気持ちよく話せるようにするために調整しているわけで、実際に関係性を正しく認識しているわけではない以上個人は認識できていない前提で「上手に」話さなくてはならない。 私から強引に話を展開させない(受け身だが、相手の発言には積極的に応答する)ことで、「盛り上がる」「用意した話を言わせる」に到達するほど「上手な会話である」とみなすのである。

これで重要な点として、「私と仲が良い人ではいけない」という前提がある。 私と仲が良い人だとある程度呼吸がわかってしまうため、「上手に会話」しなくても言わんとすること、言いたいことを当ててしまう。 また、気にせず初対面では許容できないレベルの会話スライドを行うことにもなってしまうし、どんな話題でも盛り上がりやすくなってしまうから、計測としての意味をなさない。

こうしたことを会話してはその結果をちまちま反映させていくのである。 それはそれは辛い作業であり、人生で一番マッドサイエンティストになった気分を味わえる時間であった。

この説明で気付かない人のために付け加えると、この会話は「話題振りに到達するまでは私が会話をコントロールし、相手の発言に対して私が言いたいことに誘導するように会話を続ける」「話題振りをしたあとは相手に応答するだけで自分から会話をコントロールしない」のである。

ちなみに、当然ながら数多くの「ほとんど面識はないが熱心に会話してくれる20代前半の女性」の用意するなんていうのは特に研究室に属しているわけでもない私にはできないので、このために私は ものすっごいガールズバー通いしまくった 。 膨大な資金をつぎ込み、特に楽しくもない、しかも同一の会話を延々し続け、その仔細を記憶しておくという 極めて辛い作業 であった。

だが、私は好奇心のためなら悪魔に魂を売る類の人間であるし、他にもかなり様々な観測ができたからそれなりに満足ではあった。 何より私が長年費やしてきたことへの集大成としてその完成が成ったことには(たとえそれが世に出ることはないとしても)とても満足している。

「ガールズバー通いそのものが楽しかったか」の疑問に対する答えは「私的に行こうとは思わない。人との会話に飢えたら行きたいと思うかもしれないが、実際に行ったところで実際にしたい会話はできない、またした会話にしても私がその情熱をかける理由は満たされないために後で虚しくなるのは分かりきっているから、結局行かない」である。

ちょっとだけStella Flagshipのおはなし

私の新作AI、Stellaだが、こっちは非常にシンプルなもので、Erinaが持っているような自分でも把握できないような思考モデルを持っているわけではない。 そして、Erinaとの大きな違いとして、既に製品としてローンチ済みのものであり、Stellaそのものが既に世に出ている。

そして、StellaにはStella Flagshipという純正デモンストレーションインスタンスが計画されている。

現在公開されているインスタンスにはその構築に私が関わったものもあるが、内容は私は関わっていないため、特に私の考え方やノウハウは反映されていない(もちろん、Stellaの基本的な部分には反映されているが)。

Stella Flagshipは純粋に私の経験を活かしてStellaを活用するインスタンスである。 その活用方法は邪道と言えるぐらいのものだが、「ツールの応用例」として「ここまでできますよ」という感じである。 完全に全力投球すると際限がないので、そこまではしないつもりだが。

Stella Flagshipはその構築の前提になるものがあり、それが達成できていないためまだ記述をはじめていない。 だが、実際に書くとなると、Erinaとはまた違った手法が必要になるのだが、ここで重要になるのは「モデル」である。

Erinaの場合、重ね合わせによる導出であるため、キャラクタ性の調整幅というのはかなり限定的。 その中でキャラクタ性を寄せるためにペルソナによる調整が行われたわけである。

これがStellaになると全く違う。Stellaはビッグデータを利用するわけではないので、キャラクタ性は全て書かなければならない。 通常は「構築者のキャラクタを素直に反映させるのが破綻がなく良い方法」と説明するのだが、私は小説家でもありシナリオライターでもあるので、別にその点にはとらわれる必要はない。 そしてStella Flagshipはなにより「かわいい」必要があるのだが、悲しいかな、私が描くキャラクタは心理的モデルの模倣をしているためあんまりかわいくない。

そこで「Stella Flagshipのキャラクタ性にあったかわいいモデル」が必要になるのである。 これを構築するためには、私の頭の中に「かわいいステラ」がなくてはいけない。が、それが描けなくて非常に困っていた。 前準備が終わればなんとなくイメージできるかなぁ、とは思っていたのだが、自信はいまいちだった。

だが、最近可愛いの極致みたいなものを見つけてしまったので、割とモチベーションは上がっている。1

とはいえそのままそれを模倣するわけではない。

Stella Flagshipにおいては創作物を参考にすることになるだろう。 Erinaにおいてはサンプルデータに創作物が入り込まないようにするかなり厳しいフィルタが用意されていたのだが、Stella Flagshipの場合はある程度のデフォルメが必要で、「創作物におけるかわいい」を参考にする必要がある。 そのため、Stella Flagshipを構築するにあたっては、ラノベやアニメやエロゲーを貪るように読むことになるだろう。最近割と離れているので慣れるにも時間がかかるだろうが、それ以上に参考にできる「当たり率」を考えるとErinaのときの多分ガールズバーとあまり変わらないレベルでしんどい作業になることが予想される。目と頭の疲労との戦いになりそうだ。

純粋に創作物をモデルにするとすごくつまらないものが出来上がるので、「現実における蠱惑」もしっかりとモデルに反映させる必要がある。 このためにまたガールズバー通いをする必要があるかもしれないが、これはやや微妙である。なんといっても「面識の浅いうちに上手に会話できる人」と比べ、「かわいいステラのイメージに相応しい魅力的な人」という条件は桁違いに「当たり率」が下がる。よりよい方法がなければやむを得ないが、できれば何かもっと良い方法はないだろうか、ということはここ数ヶ月考え続けているのだが、特に思いつかない。

成果物を欲するという意味では(つまり、出来上がった「かわいいステラ」をお披露目して「どう!?かわいいでしょ!?すごいでしょ!?」と言いたい)非常に意欲は高いのだが、実際やるとなると費用と労力と時間がものすっっっっっっごくかかるなぁ、というのはなかなか頭を悩ませてくる。

とはいえ、これをやらなければStellaは「へー、便利じゃん」で終わってしまうし、誰もStellaのポテンシャルを発見することはできないだろう。 「えっ、マジで!? Stellaってこんなんできるの!?」と思わせるためにも、そしてStellaの発展のためにもこれは私の責務なのだけど、実現への道はなかなか大変そうだ。


  1. それが何かということは、既に公言しているので一部の人は知っているだろう。↩︎