RubyKaigi 2024に参加できて本当に良かった

RubyKaigi 2024に参加してきました。

今回参加までに紆余曲折あったので、一時は参加を諦めていたんですが、何とか無事参加することが出来ました。

2011年に初参加して以来休まず参加していたので、ついに連続参加が途絶えるのかと思ってましたが、無事連続参加を達成できて嬉しい限りです。

今回はそういう事情もあってか、コミュニティとの繋がりを強く感じることができたRubyKaigiでした。

色々思いが溢れてしまって、技術的に楽しかったこと、自分が嬉しかったこと、参加前の事情とか全部書いてたらえらい分量になってしまいました。気が向いたら目に付いたところだけ読んでくださいw

参加前

そもそも何があったかというと、大体去年の12月ぐらいから咳が止まらなくなり、更に年明けぐらいに高熱が出た上で咳が出続けている状態でした。

余りに咳が酷かったので、喉に傷が付いた後胃酸が逆流したりして声帯の近くに潰瘍みたいなのが出来て、まともに喋ることが出来なくなりました。

一番酷い時は、水飲むだけで苦痛だったし夜は寝れないし微熱が続くしということで、休職しようかと思ってたぐらいです。

大体その状態が2ヶ月半ぐらい続いて、本当に最悪の場合は咽頭癌の類かもしれんしこれはRubyKaigiまでに回復するか分からんぞ、ということで一旦参加を諦める覚悟をして、予定を立てるのを止めました。

その後、病院で検査した結果悪性腫瘍の可能性はほぼ無いということが分かって、そこからちょっとづつ状態が回復しました。多少心理的要因もあったのかもしれません。回復傾向になるまでは、かなり精神的にもキツかったですね。

3月後半ぐらいから概ね喋れる様になったんですが、体調がまだ不安定だったのでそこでもまだ参加は決めてませんでした。

4月前後で、Asakusa.rbの花見があって、久しぶりに人とちゃんと喋ってみて、なんとか乗り越えられたので、この辺りから行けそうだなという気になってきました。

その後、RubyKaigiのタイムテーブル解説回が4月の後半にあって、行くかどうかも決めてないのに参加したんですが、kaneko.yさんとかtagomorisさんがすげー楽しそうに自分のトーク内容をアピールしてくるので、これは行く覚悟を決めるしかないなと思い、その翌日に予定を立て直しました。

後は、悪化しないことを祈って当日へ、という感じです。

沖縄は飛行機の本数も宿も多いので、4月の終わりに予約を取っても割と何とかなったのが幸いでした。

参加中

今回、そういった事情もあってTwitterを見てくれてた人達からは、えらく体調を心配されると共に「来られて良かったですね」と声をかけていただきました。

会う人、会う人に言われたもんだから、同じことばっか喋ってるなwと思ってしまった。

でも、本当に嬉しく思いました。ご心配をおかけしましたが、ちゃんと楽しむことができましたし、コミュニティの皆さんと交流することが出来て良かったです。ありがとうございました!

その他のハイライトについては、続きで書いていこうと思います。

Day0

会場近くのVAMOSで、MNTSQさんにビールを奢ってもらいつつ、kwappaさん、ryopekoさん、森田さん、makimotoさんといった面々で飲んでました。その後、福岡のjimrockさんと合流して、jimrockさんの昔の知り合いがやってたというお店にタクシーで行き、とても地元民向けっぽいお店で二次会という感じ。

この後も大体毎日イベントの合間にビール飲んで話したりする場としてお世話になった。

煮付け(アカマチだっけ?)がすごい旨かった。後グルクンの雲丹焼きが酒を吸い取る力を持っていた。

kwappaさんと自分の仕事上の立ち位置とか、自分の中途半端さみたいなものについて話が出来たのが良かった。

ryopekoさんと同じホテルだったので、最後にコンビニで缶ビール買って延長戦したのも楽しかった!

Day1

1日目が自分にとって一番セッションが濃かった気がしている。

キーノート

とにかく、ぺんさんのキーノートが圧巻で芸術的でめちゃくちゃカッコ良かった。1000人入るホールに埋まっている聴衆が全員「ん?え?何?」ってなってる空気感を感じることって人生で早々無いだろうなと思いましたね。

使える技術の一つ一つは理解できなくはないが、それをやろうとする発想力・想像力の幅の広さが全然違うんだなということを実感した。自分もそれなりにはRubyで悪さをするタイプですが、次元が違う。TRICK 2025の開催も決まったし、来年が楽しみです。人生で一回ぐらいは応募してみたいがはてさて……。

kanekoさんのセッション

また、自分が参加を決めたkanekoさん、tagomorisさんの両名の発表が1日目にあったので、それもバッチリ聞けたのが良かった。

kanekoさんの発表は技術的にも面白かったんですが、特に凄いなと思ったのがプロジェクトのゴールというか行きたい未来をちゃんと見据えていて、そのために必要なブロックをちゃんと切り出して分かり易い問題に落としていることだと感じました。今回の発表は明確に到達可能な未来が見えていて、それに向かうチームが育ったことをアピールするという目的もあったのかなと勝手に思っています。

ちなみに、自分はRubyのParserのスタンスとしては、処理系の基盤はParser Generatorで生成するべきだと思っていて、kanekoさんの説明に納得している立場です。Rubyは結構APIファーストなデザインで作られている言語だと思っていて、これはRubyを使う人に負荷をかけずに処理系の内側を柔軟に変えられる様にする意図があるのだと思ってますが、一方でAPIファーストで何かを追加しようとした時に文法が収集付かなくなる可能性もありそうだと感じていて、安全にメンテ可能な確かな基盤として、明確な文法基盤がある方が今後のメンテにとって有効なんじゃないかと考えています。

一方、MatzがASTのAPIとしてはPrismを採用するつもりと言っていて、それについても自分は賛成です。Prismはエコシステムの対応状況をかなり意識して作られているというか、現実的な今のユースケースを既にある程度解決している所に優位性があります。RubyPrize受賞の時のKevinさんのプレゼンを聞きましたが、既にParser関連の野良gemの大半を置き換えることに成功しています。Rubyのエコシステムの中で便利に使える共通のASTインターフェースが、処理系の中でメンテされるのであれば、それだけで十分役割を果たしたんじゃないかなあと思うところです。

という訳で、今の自分にとっての理想的な共生関係は、Lramaが処理系のパーサーを担ってそこからCに限らず色々な実装のパーサーを生成可能になっていて、Rubyレベル(もしくはC拡張レベルも?)でパース後のASTを扱うインターフェースはPrismが生み出したAPIでコントロールできる、という状態が良いなと思っています。まあ、一Rubyユーザーによる勝手な妄想ですがw

yujiyokooさんのセッション

初めてRubyKaigiで見た時からファンで、毎回実機のゲーム機と自作のプレゼンツールで発表自体がデモになっているというクールな発表です。 今回はWiiのモーションセンサーをドリキャスに移植して釣りコンのモーションでスライドが操作できる様になってた所が最高に変態だと思ったポイントです。 情熱とロマンを感じますね。

後になって会場でyujiyokooさんとお話しする機会があり、同じ靴(ジャングルモック)の愛用者であることが分かり嬉しかった。相変わらずジャングルモック仲間が多いRubyKaigi。

tagomorisさんのセッション

話は変わって、tagomorisさんのNamespaceですが、これは直近のRubyの中でも一番自分に刺さるキラーコンテンツでRefinements以来のワクワク感があります。

今の理解では処理系内部のクラス・定数構造の基本形をroot namespaceでfixし、main namespace、sub namespaceと段階的にcopy on writeでクラス構造を複製しつつ変更を反映させるという構成だと理解してます。

今回は実装の詳細まで降りる時間が無かった様なので、想像でしかないんですがRubyの定数参照は非常にややこしいのでその辺りの解決と、クラス構造を複製した時に思わぬところから参照が残っていて変更が伝播しなかったり、GCで消されて死ぬというのが難しそうだなと思っています。後、今後の展開で気になったのはObject Shapeとの関係かな。必要なものがコピーされてれば、後は内側で勝手に解決されそうな気はするけど、本当か?という気もする。

Namespaceは、Railsで真面目にモジュラモノリスの区分けを行う上で非常に役に立ちそうです。RDBを共通化しつつコンテキストごとにモデルを区切っても明確に範囲が限定できるはず。また、fluentdの様なプラグイン構造を持ったアプリケーションを作る上では依存ライブラリの衝突は現実的に発生しうる問題で、特にサイレントに微妙に壊れた時が一番困るが、それを解決できそうです。他にはRailsを部分的にアップグレードして影響範囲を確認したり、bindingを引っ張り出して遊んだりしてみたいですね。UnboundMethodを引っ張り出すのは流石にメソッドエントリが一致しないので使えなさそう。

更には、ドラスティックなDSLを実現するのにも使えそうです。一定の処理領域内なら組込みクラスを弄っても変更内容を閉じ込められる。この辺りRefinementsに近いところもある。Namespaceだと変更内容はNamespace全般に伝播するので、Refinementsより便利なこともある一方で、Refinementsはスコープがより限定的なのが逆に良い点になるケースもあります。状況に応じて使い分けていきたい。(Refinementsはモジュールの探索チェーンの動きがちょっと特殊なので、Namespaceの実装時のエッジケースになりそうな気もするがw)

一方で、セッション中でも語られていた様に、gemのエコシステムにおいて古いバージョンが維持される逆向きのインセンティブが発生する懸念は理解できるし、それに関連してbundlerとの統合についても懸念する意見をTwitterで見ました。コミュニティにとってより良い機能にするためには、考えることが色々ありそうで、この辺りの意見交換では自分も貢献できそうに思いました。

参考: サプライチェーンアタックの対策について以前のRubyKaigiで登壇されていたMaciejさんのツイートからのスレッド

こんな感じで実用的かつRubyの柔軟さの幅を広げることもできそうということで自分の中では一石二鳥の機能だと思っていたんですが、Day3のMatzのキーノートで4.0の目玉になる可能性が高まっており、注目度も一気に爆上がりで思ってたよりもっと凄いことになりましたね。

という訳で、今回参加の決め手になった2人のセッションはちゃんと聞けたし、考えることも色々できて本当に良かった。アピールしてただけのことはある。

懇親会

Official Partyのチケットはギリギリで間に合ったので、参加できました。インドア派の自分でもあの会場は最高だったなー。沖縄の海辺でパーティってのは絵になる。

海風で髪がえらいことになってますが、こういう髪質なんですよww

Official Partyは色々な人に挨拶して回って、2次回はアルパカさんとNamespace期待度高いっすねーという話で盛り上がっていたと思う。この辺りから酔っ払ってて細かいことを覚えていないw

Day2

キーノート

Day2のキーノートについては、今回余りRubyKaigiらしくはなかったというか、良いトークだったしKaigi on Railsだったら諸手を上げて喜んでたトークなんですが自分がRubyKaigiのキーノートとして期待する内容とはちょっと違ってた感じですね。個人的には、もっとFiberやFiber SchedulerとFalconの深いところが聞きたかった。まあ、余りハードなトークばかりを求めるのも違うとは理解していて、単に自分の好みの話ですが……。

ydahさんのセッション

Day2で特に印象に残ったのは、ydahさんのLramaの記法拡張に関するトークです。自分は去年のRubyKaigiやKaigi on Railsの参加のKaigiEffectとしてjoker1007/tree-sitter-rbsというものを実装していました。要はシンタックスハイライトに使えるRBSのParserなんですが、これはtree-sitterというParser Generatorを使って実装していて、tree-sitterはJavaScriptDSLBNFに近いコードを記述することでParserを生成します。JavaScriptDSLであることの利点は、JSの関数の組み合わせなので関数で再帰化できるし、パラメーター化もできるし、パターン化もできるということです。

これを自分で触って実感していた後に、Lramaのparameterized ruleの話を聞いて、comma区切りのlistパターンをまとめる例などを見ると、あーこれやったことあるやつだとダイレクトに理解することができました。いやー予習してて良かった。genericの様なタグ付けをしていたら型推論が欲しくなるのも理解できるし、最終的な言語非依存のParser出力のためにはCと結合したアクション記述だと不都合も多い訳で、描いている未来にも納得感はありました。

という訳で、自分の理解度が多少高かったのもあり、とても面白く聞けました。実は自分がそう感じたのには、もうちょっと理由があったのですが、それはDay3で分かります。

午後セッション

後、午後の前半ブロックは疲れていてホテルで仮眠を取ったら思ったより寝てしまい、すっ飛ばしてしまって聞けなかった……。特にhasumikinさんのPico RubyからLramaを利用する話が気になってたんですよね。というのも上記の様にtree-sitterを触ってたこともあり、Lramaから吐いたCのパーサーをtree-sitterのカスタムパーサーとして利用できれば、エディタ界隈はシンタックスハイライトのためのパーサーをメンテしなくて良くなるのでは?とか考えたことがあったからです。それで利用する側としてのLramaの使い方に興味があったんですが、残念ながら動画待ちという感じになってしまった。

LT

今回実はCFP出してたけど落ちました。まあ想像出来る理由はいくつかあるぐらい穴があったのでそれは仕方がない。

ハイライトとしてはkanekoさんの「bisonは一つしかスタックを提供していない」と言う時の楽しそうな顔ですねw 本当に最高だった。流石に高速過ぎて脳が付いていけてたか怪しいところはあったけどw

DrinkupとRubyKaraoke

今回はちゅらデータさんのDrinkupに参加させてもらいました。沖縄民謡を聞きつつ飲んでRuby Karaokeに向けて音楽モードに気持ちを切り替えるのに相性が良かった。豚の丸焼きという凄いものが登場したんですが、流石に脂も凄かったので余り多くは食べられなかった。オードブルを作ってくれたのが、どうやらエンジニア兼イタリアンのシェフというマルチタレントな方らしく、ローストビーフローストポークなど色々と美味しかった。後やっぱ地元で食べるゴーヤチャンプルは美味いですね。

そこからRubyKaraokeに合流。なんか結果的に序盤しばらく居た部屋は、ある意味で凄く「ちゃんとカラオケ」をやってる部屋だったw 盛り上がり過ぎずそれぞれ好きな歌をちゃんと歌う、という感じで割と居心地の良い部屋になったのが良かった。 上海から来た方と米津玄師を一緒に歌ったり、相生ゆらさんに歌を褒められたのが嬉しかったですね。

その後、久しぶりにお会いしたrisacanさん、nappan23さん、mishmashtanさんと合流して、ヒプマイの一人12役を披露したり、一人オオサカディビジョンやったりしてました。喜んでもらえて良かった。盛り上がってくれるととても嬉しい。

Yuryuさんと「あいつこそがテニスの王子さま」を二人でセリフちゃんと言いながら歌えたのも楽しかった。これは10年ぶりぐらいに歌った気がするw

なんかカラオケキングとか言われてたし、どうも噂に尾鰭が付いてる様な気がしなくもないww

RubyKaraokeの時は歌うのに集中してて全然写真取ってなかったので、写真が全然無かった。もし私の写真があったらTwitterなんかで送ってくれると嬉しいです!

その後、流石に病み上がりで無理し過ぎはまずいだろうとちょっと早めに抜けて、帰っている道すがらに、何故か私の様子を見に来た野生のやんちゃさんが飛び出してきた。たまたま会うだけならまだしも、自分を狙って移動している最中にバッタリ会うとは、これが「スタンド使い同士は引かれ合う」というやつかと実感し、そのまま閉店間際のステーキヒカルに直行。二人で締めのステーキを食べることに成功しました。

この辺りで、もうRubyKaigiに来た元は完全に取れたなと確信している。

Day3

RubyKaraokeから戻った後、入眠障害が発動して睡眠に失敗した結果、午前のセッション時間に活動できないという失態。and the Worldがーーー!!とりあえず、黒曜さんのツイートを追っかけることで当日の話題に付いていくことは何とかできた。後ですれ違った時にお礼も言えて良かった。

実はSTORESさんのRuby Quizの3日目のネタに自分の名前が出てました。当日はネタバレになっちゃうので言及しませんでしたが、この記事が元ネタですね。ちょっと嬉しかったw

アーロンさんのセッション と sekiさんのセッション

Day3はアーロンさんの赤黒木の話が面白かった。昔Haskellを練習している時に赤黒木を書いたことがあったんでパターンマッチと非常に相性が良いということが分かっていたので、実装の流れはイメージしやすかった。そしてObject Shapeの性質である、基本的にキャッシュを残し続けて削除は無い、リードが多いので検索効率が大事、メモリ効率を要求するといった特性が、赤黒木のデータ構造自体が持つ性質と非常に相性が良いことが良く分かる良い発表だった。アルゴリズムを学ぶことのパワーが伝わってくる話だった。適切な知識があれば、より安全に効率よく問題を解決できることというのはしばしばあって、そのために使える材料がコンピューターサイエンスの基礎知識に転がっている。自分もまだまだなところは一杯あるが、改めて大事だなと思えた発表だった。

sekiさんのERBの話ですが、聞いてるとやっぱsekiさんのプロダクトはオーパーツなんだよなーと思えて仕方ないですね。captureの解決策とかクール過ぎるし、今から見ても有用性に対する目の付け所が良過ぎる。binding#local_variable_getの実用的な活用法が見れます。

Matzキーノート

MatzのキーノートはやっぱNamespaceへの思いを話してくれたのが熱かったですね。その話題もあってかRuby4が見えてきたかなという印象でした。そして自虐ネタは封印する様にしたらしいw まあガッカリされると言われている理由は分からんではないし、一方でそんなこと言う輩が居るがちゃんと現実を見てから言ってる?みたいなこと言いたくなるMatzの気持ちも分からんではないw

tagomorisさんの仕事の注目度が爆上がりしたのは良いことだけど、これはプレッシャーも凄そうだなーと思う。微力ながら応援しております。(ちょうどこの後の懇親会でJustinと話してた時にそういう話になった)

AfterParty

色々話せて良かったんですが特にハイライトとして覚えているのは、ydahさんと遭遇して話をした時に、ydahさんとkanekoさんの発表の想定リスナーの設定が私(joker1007)だったらしいと聞かせてもらったことです。Kaigi Effectで触発されてRBSのパーサーを書く様な人を狙って話を作れば、面白い話になりそうだし今後の仲間を探すのにも有用だと思ってもらえた様です。実際AfterParty会場でそういったtree-sitterの動きについて理解したことをydahさんに伝えることも出来たし、道理で聞いてて面白かった訳だと思いました。ちゃんと聞きに行けて本当に良かった。

追記: 改めて聞いたらydahさんが金子さんにレビューしてもらってた時にそういう話になったということでした。なので金子さんの方の真の想定リスナーは不明ですw でも近いところにあったのかもしれないなーと勝手に思っている。

後、上の階にあったカラオケ付きのバーカウンターで万葉のkukoさんと念願のカラオケが出来ました。(喉壊してたのにまた歌ってんのかって感じだけどw) 魂のルフランを一緒に歌えて楽しかったー。kukoさんめちゃくちゃ歌上手いし、お酒入ってない時に改めて行きたいなあ。

その後の二次会は、結構人数が居たんですが自分の居たテーブルがやばくて、kateiさん、なるせさん、がちゃぴん先生、ogijunさん、どみにおんさん、という感じでした。LLVM&RubyのコミッターとLinuxカーネル&Rubyのコミッターがそれぞれ自分の仕事はそんなに難しくないですよーってお互いを褒め合っていて、周りが「簡単な訳あるかー!」となってたのが本当にRubyKaigiだなーという感じで楽しかった。

その後、何故か自分がなるせさんにお前はやる気出してコミッタになるんだ!とすげー激詰めされて必死に回避してた気がします。いや、やる気が無いとか無理とかまでは思ってないのですが、心の準備とか覚悟というものがですね……。

まあ、何にせよとてもRubyKaigiらしい懇親会に参加できて最高としか言えない夜でした。

帰りがけの羽田空港で出会った最後のスタンド使いkakutaniさんとのツーショットで締め。出来過ぎだったな。

まとめ

今回、参加自体が自分の中で危うかった経緯もあって色々な人に心配をおかけしてしまいましたが、何とか元気そうな姿が見せられて本当に良かったと思っています。本当に会う人会う人に心配されてたので申し訳なかったw

(実は今もちゃんと回復したとは言えない状態で、喉の違和感や続きそうな痛みには結構注意を払っている状態です。覚悟の上でしたがRubyKaigiでかなりの負荷がかかったことは間違いないので、また状態が悪化しないかしばらく気を張ることになるでしょう。)

また、最近仕事で余りRubyを使わない領域で仕事をしているものでRuby自体とは若干関わりが薄くなっていたのですが、パRailsに関してフィヨルドの生徒の方から今年も沢山の感謝の声を聞かせてもらいましたし、ここ最近での数少ないアウトプットだったtree-sitter-rbsの活動がパーサーギャング団の想定リスナーの材料になったり、これのおかげでVSCodeからneovimに戻れたので本当に嬉しいと万葉のosatohhさんから感謝の言葉を頂いたりしました。

自分としてはちょっとしたことしか出来てないと思ってましたが、ちゃんと誰かの役に立ってたことや伝わっていたことが実感できてとても嬉しかったし、こちらの方こそ感謝しかありません。本当にありがとうございました!

コミュニティの皆さんとの繋がりを改めて実感し、RubyKaigiというのは本当に良いイベントで、自分にとっても大事なものだったなということがよく分かりました。

本当に参加できて良かった。楽しめて良かった。多くのRubyistに会えて良かった。今年もありがとうRubyKaigi!来年も無事参加できます様に!

偏りに満ちたWayland時代のLinux Desktopおすすめアプリ 2024

最近、wayland移行も大分安定して、デスクトップで利用するアプリも多少変化したので、最近利用しているものをまとめておこうと思う。 基本的にGNONEやKDE Plasmaみたいな重厚なデスクトップ環境は使わないタイプなので、そういうラインナップになっている。

開発で必須、みたいなやつはこの記事には余り入れてない。

ウインドウマネージャー

Hyprland

waylandで動作するタイル型ウインドウマネージャー。ヌルヌル動くのが気持ち良いだけでなく、機能的にもよく出来ている。開発が活発なのも良い。 waylandで画面共有のために利用されるxdg-desktop-portalのために独自実装を持っていてswayでは出来なかったウインドウ単位の画面共有が出来る。

という訳でwaylandのタイル型ウインドウマネージャーといえばswayがメジャーだが、最近はHyprlandを利用している。

IME

fcitx5

元々ibusユーザーだったのだが、waylandの入力プロトコルへの対応状況ではfcitxの方が良さそうに感じる。

ブラウザ

Firefox

Chrome及びChromiumはwayland対応が非常に微妙で、XWayland経由で起動しないと日本語を打つのにめちゃくちゃ苦労する。 英語しか打たないなら気にしなくていいのだが。 なので、Wayland環境ならFirefoxを使う方がいい。ベンダーニュートラルなのを応援するのも大事。

ステータスバー

waybar

wayland対応のステータスバー作成アプリ。設定が簡単なのが良い。やたら凝ったことをしないならこれで十分。

デスクトップ通知

dunst

wayland環境でもちゃんと思った通りに動作するし、デスクトップ環境に依存しないのが良い。

ターミナル

alacrittyも悪くないのだが、footの方はwayland専用で若干alacrittyより高速らしい。 sixel対応しているのが良い。

chafa

画像をiterm形式かkitty形式かsixel形式かsymbols(ターミナルの色指定で無理矢理出すやつ)の形式に変換して表示してくれる。 とりあえずターミナルで画像が見たい時は使える。sixel対応のfootだと便利。 とりあえずターミナルで画像を簡単に見たい時に使える。

delta

diffをside-by-sideで出せる様になるやつ

zoxide

autojumpとかzのモダンなrust実装。fzfが入ってれば履歴からも簡単に探せてディレクトリジャンプ出来る。

starship

入れるだけでshellのプロンプトを良い感じにしてくれるプロンプト生成ツール。 相当こだわりが無いならこれで良いと思う。

sheldon

汎用的なshell plugin管理ツール。zshrcの整理に使っている。。

pueue

CLIの実行をキューイングして、任意の並列数で順番に実行してくれる。たまに便利。

シェルのチートシート管理ツール。AWSのコマンドの連鎖とかを記述できるので、aws cliでlist upしてfuzzy finder噛まして検索したarnに対して操作コマンドを実行みたいなスニペットを簡単に書ける。

スクリーンショット

grim + slurp + swappy

slurpで指定したデスクトップ領域をgrimで画像にしてswappyで加工・書き込み。 コマンドにすると以下の様になる。

grim -g "$(slurp)" - | swappy -f -

色々必要でややこしいので、flameshotのwayland対応を使うのも良いかも。

ターミナルファイラー

yazi or ranger

基本的にはrangerで良いと思っているが最近余りリリースが無いので、新しいもの好きならyaziが良いんじゃないかと思っている。 rustで実装されていてやたら早いのが売りだが、MIME特定にfileコマンドを利用していて、NASにアクセスするとそこが遅い。 拡張子だけでMIMEを決めるプラグインがあるのでそっちを使う手もある。

GUIファイラー

thunar

ほとんど使わないのだが、ブラウザなどにD&Dしたい時にターミナルファイラーだけだと困るので用意してある。他のファイラーだとwaylandで動かなかったり、GNOMEとかKDEの必要無い依存がガッツリ入ったりするので、バランス取れてるのがthunarだった。

動画プレイヤー

mpv

これは昔から変わらない。これ一択だと思う。

音楽プレイヤー、オーディオ関連

mpd + cantata

mpdならDSDのハードウェアデコード再生が出来るし、システム移行しても潰しが効きそうってことで採用している。まあ、そんなDSDハイレゾ音源ほとんど持ってないんだけど……。 再生のフロントエンドはcantataを利用している。開発がほぼ止まっているのだが、これより便利なフロントエンドが無い。ここは不安な点の一つ。

qpwgraph

pipewireの接続状態を可視化してくれるツール。GUIで音声出力先を一時的に切り替えたり、pipewireのプラグインとして実行しているノイキャンに繋いだりバイパスしたりできる。

音源タグ編集

kid3-qt

特筆すべき点は無いけど必要十分という感じ。

画像ビューワー

vimiv-qt or nsxiv or mcomix

waylandネイティブで起動できて軽さとシンプルさで選ぶとvimivだった。 wayland対応にこだわらないならnsxivでも良い。 zipにまとめた画像ファイルはmcomixで見ている。

壁紙設定

swww + waypaper

wayland環境での壁紙設定にはそれ用のアプリが要る。 hyprpaperとかswaybgの方がシンプルな気はするけど、壁紙切り替え時にエフェクトが入るという地味かつ無駄なオシャレ感でswwwを利用している。 壁紙の選択はwaypaperがswwwバックエンドに対応していたのでこれを利用している。

mpvpaper

動画ファイルを無限ループする壁紙にしてくれる。live wallpaperで検索するとオシャレな壁紙が手に入る。 タイル型マネージャーを使ってると、正直リソースの無駄なのだが、オシャレ感が欲しい時にw

ランチャー

wofi

シンプルで応用が効くし、そもそもがwayland向けなのでrofiみたいにwayland forkを利用したりする必要がない。 desktopエントリの起動だけじゃなくてPATHにあるコマンドの起動にも使える。

クリップボード管理

wl-clipboard + cliphist + wofi

wl-clipboardで変更をwatchして変更があったらcliphistに溜める。 キーボードショートカットでcliphistから読み出したものをwofiに渡して表示する。選択したらそれをclipboardに書き出す。という感じ。 ただ、cliphistに溜めたやつを細かく制御できないので、パスワードマネージャーからコピーした値などにセキュリティ上の懸念が多少ある。

Bluetooth

blueman

linuxBluetoothを使うなら、まずbluezが必要でそのCLIだけで十分な場合もあるが、GUIで操作したい時はbluemanを使っている。 GTKに多少依存しているぐらいでシンプルなのでデスクトップ環境を選ばないのが良い。余計なものを余り入れなくていい。

プレゼンツール

pdfpc

PDFをkeynoteのプレゼンモードみたいに表示できるツール。発表者メモも書ける。

VPN

tailscale

これを入れておけば困ることはない。革命的に便利過ぎる。個人用途なら大抵の場合無料で十分。200台ぐらいデバイス持ってる変態はお金出してくださいw

20年Rubyを触ってきて初めて踏んだattr_readerのケツカンマ問題、あるいはdefの返り値がシンボルであることの問題

今日Rubyを書いていて、なんじゃこれと思った動作があった。

試しに以下のコードを実行してみて欲しい。

class Foo
  attr_reader :hoge, :fuga,

  def initialize(a, b)
    p a
    p b
  end
end

Foo.new

実行してみると分かるが、これには例外が出ない。

initializeで定義した必須引数はどうなったのか?

よくよく見るとattr_readerの引数の末尾に,がある。

つまり、このコードは分かりやすく書くと以下の様になる。

class Foo
  attr_reader(:hoge, :fuga, def initialize(a, b)
      p a
      p b
  end)
end

Foo.new

このコードを更に分かり易く書くとこうなる。

class Foo
  def initialize(a, b)
    p a
    p b
  end

  attr_reader :hoge, :fuga, :initialize
end


Foo.new

initializeが定義され次の瞬間にattr_readerで上書きされてdef initialize; @initialize; end相当の状態になる。

そりゃそうだという感じだし、何もおかしなことはない。ないのだが!これは結構怖い。

という訳で、attr_なんちゃらの引数のケツカンマには気を付けよう。 (というかシンボルを受け取ってメソッドを定義する類のメタプロコード全般)

自分で書く分には意外とこうはならないのだが、copilotみたいな奴の補完でざっくりと書いてしまうと、ケツカンマ以外は問題無い上にシンタックスエラーも出ないので、「はあ?!」という動きになってしまう。というかなった。

バルダーズゲート3が素晴らしかったのでオススメポイントを書く

年末からプレイしていたバルダーズゲート3をようやくクリアした。大体130〜140時間ぐらいかかったと思う。最近のゲーム体験としては、トップクラスに入る神ゲーだった。ここ数年だとOuter Wildsに匹敵するレベル。ゲーム史に残るRPGと言っても過言ではない。

ただこの作品は、とにかく序盤がハードなのと自由度が異常なので、慣れない人にはかなり取っ付き辛いらしい。それを差し置いてもオススメしたいので、どういうところがオススメなのかと何がハードなのかをちょっと書いてみようと思う。

前提として、バルダーズゲートシリーズはTRPGのダンジョンズ&ドラゴンズの世界観に基いたゲームで、ちなみに自分は元々D&Dは存在ぐらいは知っていたしTRPGについてはある程度の知識と経験がある程度である。

結論から書いておくと「自由度」と「分かりやすさ・親切さ」のトレードオフを受け入れられるかどうかが、このゲームの肝だと思う。

TRPG感の再現

流石にコンピューターゲームなので限界はあるのだが、会話や行動の選択に対してとにかく自由度が異常に高いし、その場の会話だけでなくちゃんと続きの展開や有利不利に影響する。そして場合に依っては取得している技能によってダイス判定が入る。得意なことはキャラごとに違うので、仲間と旅をすることにちゃんと意味が感じられる。会話の流れ次第では良い人間にもなれるし、めちゃくちゃ嫌な奴にもなれる。ちゃんと一貫したキャラ設定を想像してプレイしていると没入感が高まってとても良い体験が得られる。この辺りのロールプレイ感はTRPGの良さを上手く活かせてるなあと感じる。

悪事的なことだと、めちゃくちゃ高値を吹っかけてくるキャラの話を一回断わった後で部屋に忍び込んだりスリで入手したアイテムで突破したり、商人にワイロを渡して評価を上げた後にスリで金を取り戻すとか悪辣なこともできる。こういう自由度に対して頭を捻るのが楽しい。ペテンにかけて信用させてから後ろから刺すとか。

一方で、分かり易い解決策は必ずしも楽ではないし、報酬が良いとも限らないので、効率プレイ大好きみたいな人には余り楽しくないかもしれない。

探索とクエスト展開のバリエーション

バルダーズゲート3は昨今のオープンワールドRPGの様な探索とサブクエスト受注みたいな流れはあるのだが、その手のゲームの様にマップ上に依頼者のマーカーが出てたりとかは一切無い。町の人と喋ってたらいきなり問題に巻き込まれたり、頼みごとをされたりするし、その辺に落ちてるメモからクエストが開始することが多々ある。(目的地が明確な時はマーカーが出る)

なので、クエストラインの途中から合流したり、いきなり解決に近いところから始まったりするし、あるクエストが別クエストの目的と最終的に重なるケースもある。なので開始時期がズレるとイベントそのものが消失したり、報酬を貰い損ねたりする。

これも自由度とある意味で不親切な所のトレードオフで、自分としては世界を探索してそこに関わっている感じが強く感じられるので好きだったが、この点に関してはクエスト発生ポイントに関しては攻略情報を確認して、後は流れに任せるみたいなやり方がバランスよく楽しめるのかなーと思う。

めちゃくちゃ長い期間継続するクエストとか、探し物をするクエストは実際何も攻略情報が無いと、いつまでかかっても終わらないみたいな状態になるので、諦めてある程度割り切った方がいい。

とにかく選択を迫られたり取り返しがつかないことが多いので、いやそういうつもりじゃなかったとか、ここでそのダイス運は勘弁してくれ、というのを避けるために手癖でクイックセーブする様にしておくことをオススメする。盗みに入る前にセーブしとくとかちょっとズルいけど自分としては許容範囲。オートセーブポイントはかなり少ないので、セーブを忘れているとやり直したくなっても結構前に戻されたりする。まあ相当後になってからじゃないと選択の結果がどうなるか分からないケースもあるので、そういう時は諦めよう。

(マックス難易度だとシミュレーションゲームの鉄人モードみたいなのがあって、セーブ&ロードが禁止されるという非常にシビアなモードもある)

戦闘のシビアさと戦略性

戦闘は昔のゲームだとタクティクスオウガとかFFタクティクスに近い。ターン制で位置を移動して近接、遠距離、魔法などを選択して戦う。高所の概念や明るさや地形効果などもあるし、その辺に落ちてるオブジェクトも大抵攻撃可能だし、即席攻撃というシステムで拾って投げたりできる。敵の死体をぶん投げることもできるし、高所から突き落とすこともできる。

普通に殴って魔法撃ってもちゃんと楽しいが、濡らして電撃かけるとダメージ2倍になったり、火薬に火炎ぶつけて爆発させたり、飲んだくれてるコボルトの零したアルコールに火を付けるとか、そういう楽しみ方が出来るので、戦略性もかなり高い戦闘が楽しめる。

敵の近くで隠れて、沈黙フィールド貼ってから暗殺したり、バフかけてから奇襲して有利に進めたりできる。逆に敵が待ち伏せしていて知覚判定に成功しないと、見えないとかもある。

一方で、そういうことに関するガイドは余り無いので、何が出来るかは多少攻略情報を参照した方がいいかもしれない。

この作品では、特に序盤の戦闘がきついのが挫折を生みがちなポイントである。ラノベゴブリンスレイヤーではないが序盤のゴブリン集落に何も考えずに突っ込むと、難易度ノーマルでも余裕で全滅するし1戦1戦で全力出さないと普通に死ぬぐらいの戦闘になる。特に数の暴力に負けるので、序盤から上手く状況をコントロールする必要に迫られるのだが、序盤なので何が出来るのか自分が分かってないという辛い状態が続く。

多分、ここで心折れる人が多少居ると思うのだが、ここを突破すると割と楽になるので、諦めるのは勿体ないなと思う。

これに関しては、諦めて難易度を一時的にイージーまで下げるというのもアリだと思う。自分は最初昨今のゲームの難易度だとハードぐらいがちょうどだろと舐めてかかったら、全然無理でノーマルに変更してギリギリみたいな感じだった。まあ、これもやり応えの裏返しみたいなものではある。

中盤移行はやれることが増えて、強力なバフも使える様になるので慣れてくると大分戦闘は楽になる。基本的に守るより攻めた方がいい。先手必勝でとにかく数を減らすか強敵をぶちのめすのが定石だと思う。

後、何回かやらかしてリトライした点として、盗みがバレて戦闘になったり、範囲魔法でNPC巻き添えにするとそのまま死んで取り返しが付かなくなったりするので、その辺りはちゃんと考えて戦う必要がある。

序盤に覚えておいた方がいいTips

戦闘に関わらず、このゲームはRTボタンのメニューからいつでもターン制モードに入れる。隠密行動で仲間を順番に移動させたり罠を解除するまで仲間を待たせたり、敵の視線を回避する時には必須なのだが、自分は最初これを知らなかったので、無駄に罠を踏んだりした。

また、戦闘中に仲間のターンが連続している場合、行動順は自由で行動完了前に切り替えたりできるので、先に移動して位置をズラしてから範囲攻撃とかも出来る。これも最初気付かなくても行動を無駄にしたりしていた。

クラス選択とレベル

キャラメイク時にクラスを選択するのだが、クラスの選択によって基本能力値が決まるシステムになっている。クラスの幅は結構広いのだが、このゲームは魔法がとにかく分かりにくい。使う魔法を習得魔法の中から「準備」して利用可能状態にしておかないと使えないクラスと、常に習得魔法全てが使えるがそもそも習得できる魔法数が少ないクラスとがあり、また魔法使用回数を回復する方法もかなり限られている。なので、使い所はある程度考える必要があるし、クラスによっては習得魔法を厳選しないと非常に辛いことになる。

この辺のシステムは、TRPGのD&Dの方のシステムをある程度踏襲した作りになってるのかなーというイメージ。魔法スロットや事前準備の概念はその辺から来てると思う。この辺りもゴブリンスレイヤーの術士のイメージが近い。(もちろんD&Dの方が元祖だが)

また、このゲームは最大レベルが12なので、1レベル上がるだけでめちゃくちゃ強さが変化する。特にLV5〜6ぐらいになるとかなりスキルが揃うので、大分便利になる。

クラス選択では基本クラスに加えて、レベルアップ時に別のクラスを取得してレベルを上げることができる。12LVの中でやりくりすることになるので、最終的にファイターLV9とローグLV3みたいなことになる。シナジーの大きい組み合わせもあるので、特化したスキルが覚えられなくなるが上手く組み合わせるとめちゃくちゃ強くなる。まあ強さを重視すると、ある程度お決まりのパターンはあると思う。投擲バーバリアン+ローグ(シーフ)とかモンク+ローグ(シーフ)とかめちゃくちゃ強かった。

そして、100ゴールドでリスペックするシステムがあってクラス変更とレベルの上げ直しが可能なので、かなり簡単にスキルの組替えが出来るので、中盤ぐらいからガンガン状況に合わせてクラスを変えたり、各クラスで出来ることを調べて自分なりのビルドを考えられる様になる。流石に、色々なクラスで何が出来る様になるのかを自分で全部調べるのはしんどかったので、クラス情報に関しては中盤辺りからめちゃくちゃ攻略情報を参照した。

後、主要な登場キャラのクラスチェンジも可能だが、主要キャラのシナリオとクラスが関連しているので、主要キャラのクラスをゴリゴリ変えるとちょっと世界観に影響が出るかもしれない。自分は基本クラスは変えない様にプレイしていた。

前述した様にクエスト展開の自由度の高さで、戦闘以外の技能や魔法が有効なケースもかなり多いので、戦闘特化以外のキャラ育成や万能タイプのキャラを作るのにもちゃんと意味があるのはとても良い点だったと思う。特に盗みと会話スキルはめちゃくちゃ利用頻度が高いし、動物と会話したり死体から情報を得る魔法もしばしば便利なので誰かが使える状態になっていると有用な情報やアイテムの場所が分かったりする。この世界の動物はかなり賢いので喋ると結構面白い。やたら哲学的な猫とか、100年ぐらい生きてそうな牛とかが居る。

世界観の理解の助けになるもの

正直、D&Dを今からがっつりプレイするのは、そういうコミュニティに居ないとかなりきついので、wikipediaでD&DのForgetton Realmという世界について調べて世界設定やゲームシステムやアラインメント(人格特性みたいなもの、所謂「秩序」か「混沌」か、「善」か「悪」かの概念)について調べておくと取っ付き易くなると思う。他には話に結構絡んでくる九層地獄の設定とか。

後、去年だか一昨年だかに公開されていたD&Dの映画は世界観がほぼ共通なので、プレイ前後で映画を見ると共通の語彙や人名が出てきて面白い。例えば映画に出てきたメインキャラのウィザードの祖先のエルミンスターという大魔導師の名前とかハーパーとかサーイ人がどんな奴らかとかは、ちゃんとゲーム中に出てくるし、ドルイドの能力とかも共通している。ゲームをやってから映画を見ると、かなりD&Dの要素を映画に詰め込んでることが分かって相乗的に楽しめた。

総評

簡単にバルダーズゲート3の良い点と取っ付き辛い点や分かりにくい点について書いてみたが、全体としてこのゲームは本当によくできていて、自分の中ではRPGというジャンルでは現状最高峰に位置するゲームだったと思う。特に序盤の辛さで心折れると非常に勿体ないなと思うので、頑張って乗り越えてプレイして欲しい。

ゲーム的な問題点として大きかったのはセーブ&ロードが遅い点。結構ロードしたくなるケースは多いしリトライしたいことも多いんだけど、ここでちょっと待つのでプレイ時間が伸びる。結構これで時間食ってると思う。スパイダーマン2の異常なファストトラベルの速さとかを体験した後だったので、これに関してはちょっとストレスがあった。

後は大抵が「自由度」とのトレードオフなので、冒頭にも書いたがそこを楽しめるかどうかが一番重要なポイントだと思う。自分としてはゲームというのは自分で操作しているから良いのであって、そこで自分なりの関わり方を選択できるこういう作品はゲームの醍醐味を十全に活かしていてとても良いと思う。

久しぶりに人に語りたくなるゲームだったので、珍しくこういうネタで記事を書いてみたが、気になった人は是非プレイしてみて欲しい。(実はOuter Wildsの時も語りたいことは一杯あったのだが、何書いてもネタバレになりそうで「とにかくやれ」としか言えなかったw)

tree-sitter-rbsのサポートがnvim-treesitterにマージされました

先々週ぐらいからちょっとづつ作業してテストケースを追加したりしていたtree-sitter-rbsの実装が一段落して、普通に使う分には大抵のrbsはパースできるだろう、というところまで出来たかなと思ったので、nvim-treesitterにパーサー追加のプルリクを出しました。

爆速でレビューしてもらえたので、プルリク出してから数時間で無事マージされ、tree-sitter-rbsが簡単に導入可能になりました。 🎉

久しぶりにちゃんとOSSっぽいことやったかなという感じです。

nvim-treesitterはneovim上でtree-sitterで構築されたパーサーを使ってシンタックスハイライトを行う時に必要になるものです。 従来の正規表現ベースのhighlight記法よりも、より文脈に依存したシンタックスハイライトが可能になったり、シンタックスツリーに対するクエリが可能になることを利用したアウトラインプラグインやコードブロック操作のプラグインに対応可能になります。

(既にめちゃくちゃ利用されてると思うんですが、一応neovimの機能としてはまだexperimentalではあるらしい。)

今回、nvim-treesitterの中にtree-sitter-rbsの情報が取り込まれたので、nvim-treesitterのTSInstall rbsコマンドでrbsのパーサーがインストールできる様になり、追加のプラグイン無しでリッチなシンタックスハイライトが使える様になりました。

neovimでrbsを書いてrubyに型を付けたいという方は、是非試してみてください。(まだバグがありそうな気はする)

ちなみにrbsにはannotationという仕様があるんですが、どうやって使うものか全然分かってないので、この記法だけはまだサポートしていません。どうもsteepにあるサンプルを見る限りでは、メソッドの頭に付与して副作用の有無といったメタデータを指定する類のものの様ですが……。

rbsのtree-sitterパーサを書いて、neovimのシンタックスハイライトに利用する

皆さん型書いてますか?私はそもそもRubyを書いていません!

とはいえ、最近Kaigi on RailsやRubyWorldとカンファレンスが続いていたので、ちょっとやる気を出してrbsを書くためのエコシステムに貢献しようと思い、rbs用のtree-sitterパーサを書いてみました。パーサ流行ってますからね。

github.com

READMEにしたがってnvim-treesitterでパーサをインストールし、このリポジトリをneovimプラグインとしてインストールすれば、rbsシンタックスハイライトがイカした感じになります。

しかし、しかしながらですね、これ半年ぐらい前に調べた時には誰も書いてなかったんですが、8割ぐらい書いた所で、既に別のtree-sitter-rbsがあることに気付いたんですよね……。

github.com

まあ、せっかく作ったんで完全に同じ車輪の再発明だろうが、tree-sitterでパーサを書く練習だと思って一通り書くところまでしっかりやりました。

自分の方の特色としては、neovimフレンドリーなところですかね。ちゃんと動きそうならnvim-treesitter側に取り込んでもらうとこまでやりたいところです。

というかそもそもvim-rbsってプラグインがあって、概ねそれで十分なんですけどね……。

rbsのパーサって、Ruby本体に比べたらめちゃくちゃ簡単な方だと思うんですが、それでも結構大変だったというか、評価内容が衝突するところは一杯あって、tree-sitterでどうやって解決するのか結構悩みました。結果的に動くものは出来たはずなんですが、やり方として正しいのか本当のところよく分からんですね。

本当にlramaとかprismとか正気か?と思いますよ。凄さが実感できますね。

tree-sitterとは

ここから、そもそもtree-sitterって何?という人のためと、自分の備忘録のためにtree-sitterの解説を書いていきます。

tree-sitterとはrustとCで書かれているパーサジェネレータです。パーサジェネレータ自体は概ねrustなんですが、文法の記述はJavaScriptによるDSLで行います。

tree-sitter-cliをインストールしてtree-sitter generateコマンドを使うと雛形のプロジェクトが生成されます。

トップディレクトリにgrammer.jsというファイルがあるので、そのファイルに文法を記述していきます。

tree-sitterはGLR parsingアルゴリズムを基盤にしていて、概ねBNFみたいな感じで文法が記述できます。主なDSLは以下の通り。

  • 文字列 or regex: その文字列か正規表現にマッチする
  • seq(rule1, rule2, ...): 引数で指定されたルールが順番通りに全てマッチする
  • choice(rule1, rule2, ...): 引数で指定されたルールのいずれかにマッチする
  • repeat(rule): 引数で指定されたルールの0回以上の繰り返し
  • repeat1(rule): 引数で指定されたルールの1回以上の繰り返し
  • optional(rule): 引数で指定されたルールが0回か1回マッチする
  • token(rule): 引数で指定されたルールを一まとまりのトークンとして扱う。デフォルトの文字列は1文字づつ別々のトークンの集合として扱われる。
  • token.immediate(rule): スペースなどのextras要素を間に含まずに即座に表れるトークンに対してマッチする。

その他、precedance(優先順位)を指定するDSLがあります。prec(number,rule), prec.left([number], rule),prec.right([number], rule)`で、優先順位をしたり左結合優先か右結合優先かを指定できます。二項演算子の結合順序を指定したりなんかが典型的な使い方ですね。

JSなので当然再起を使ったり、任意の関数でラップしたりできます。

Tree-sitter|Creating Parsers

Goっぽい簡単な型宣言にマッチする文法は以下の用に書けます。

module.exports = grammer({
  rules: {
    type: $ => choice($.primitive_type, $.array_type),

    primitive_type: $ => choice("int", "bool", "string"),
    array_type: $ => seq("[", "]", $.type,
  }
});

こんな感じで$.rule_nameという記述で定義済みのルールを参照できます。関数にwrapされてるので定義順は自由です。これを積み重ねてルールを書いていきます。

今回はrbsリポジトリにあるsyntax.mdBNFを、ストレートにtree-sitter記法に書き起こしてconflictが起こるところを都度調整して直しつつルールを記述していきました。

tree-sitterにはexternal scannerという機能もあり、これを利用すると、CまたはC++レキサー処理のロジックを実装することができます。DSLでは表現し切れないパーサを実現したい場合は、こちらに処理を移譲することで何でもできそうです。 今回書いてみたrbsのパーサでは必要無いものでしたが、tree-sitter-rubyではC++で書かれたscannerが利用されています。

tree-sitterのテスト

tree-sitterにはパーサのunit testを行う仕組みが用意されています。 test/corpus/ディレクトリ以下にテキストファイルで以下の様に記述します。

==================
class type
==================

type foo = String

---

(program
 (type_alias_decl
  (alias_name
   (variable))
  (type
   (class_type
    (class_name
     (constant))))))

==================
namespaced class type
==================

type foo = Foo::Bar::Baz

---

(program
 (type_alias_decl
  (alias_name
   (variable))
  (type
   (class_type
    (class_name
     (namespace
      (namespace
       (constant))
      (constant))
     (constant))))))

この様に記述してtree-sitter testコマンドを実行すると、コードの断片をパースして、結果のシンタックスツリーを期待した構造と比較しテストできます。

tree-sitterによるシンタックスハイライト

tree-sitterにはqueryという仕組みがあります。(https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries)

このクエリを利用してマッチする構造にマーキングしていくことでハイライト対象を認識します。

queries/highlights.scmというファイルにそのルールを記述します。

tree-sitter-rbsのルールの一部を抜粋すると以下の様な形になります。

[
  "class"
  "module"
  "interface"
  "type"
  "def"
  "attr_reader"
  "attr_writer"
  "attr_accessor"
  "end"
  "alias"
] @keyword

[
 "def"
] @keyword.function

(include_member "include" @function.method)  
(extend_member "extend" @function.method)  
(prepend_member "prepend" @function.method)  

特定の文字列のトークンとマッチすれば@keywordというマークが付きます。include_memberというツリー構造の中で"include"が出てきた時には@function.methodというマークを付けます。

このクエリには構造マッチ以外に追加で条件を加えることもできます。

(
  (pair
    key: (property_identifier) @key-name
    value: (identifier) @value-name)
  (#eq? @key-name @value-name)
)

この例では、pairというツリー構造のkeyというフィールドとvalueというフィールドの中身が同じ時にだけマッチします。

どういうマークを付けるとneovimで利用できるかは https://github.com/nvim-treesitter/nvim-treesitter/blob/master/CONTRIBUTING.md に書かれています。

シンタックスハイライトのテスト

tree-sitterではシンタックスハイライトもテストが可能です。

test/highlight/ 以下にソースコードを記述します。この時ちょっと面白い書き方をします。

class Foo[A]
  # <- keyword
        # <- type
          # <- constant
  def foo: (String) -> String
   # <- keyword
      # <- function.method
              # <- type
                        # <- type
end

この様にして、下の行にコメントを追加し、そのコメントの開始地点に期待されるマークが何なのかを記述します。 一つ一つの指定がアサーションになり、期待値と一致しているかを検証してくれます。

neovimでハイライトを使う場合の注意点

neovimで利用する場合、nvim-treesitterというプラグインを使うのですが、nvim-treesitterは微妙にtree-sitterのプロジェクト構造と要求するファイルの位置が異なっています。

具体的にはrbsのパーサであれば、neovimのruntimepathのいずれかの中にqueries/rbs/highlights.scmという形で配置する必要があります。これが微妙に面倒臭いので、理想的にはnvim-treesitterにPRを出して取り込んでもらうのが良さそうです。

今のところ自分が書いたtree-sitter-rbsでは、queries/rbs/以下にクエリファイルを置きつつ、tree-sitter testで認識できる様にシンボリックリンクを貼って調整しています。 こうすることで、neovimのパッケージ管理マネージャーでインストールし、パーサをコンパイルするだけで利用できる様にしています。

最後に

今回、tree-sitterならDSLあるしrbsのパーサぐらいならサクっと書けるんじゃないかと思って書いてみましたが、それでもまともに動くものを作るのに2日ぐらいはかかりました。rubyのパーサなんか絶対自分じゃ書きたくないですねw 面倒臭過ぎる……。 そんなパーサをゴリゴリ改善していってくれているKevinさんと金子さんには尊敬の念を禁じ得ません。

tree-sitter-rubyはかなり頑張っているパーサなのですが、それでもパース不可能なケースはいくつかあります。こういったユニバーサルパーサ実現に向けた流れの中で、tree-sitter-rubyに相当するものが自動で生成される様な世界になるとテキストエディタ界隈にもメリットがありそうです。

Railsで秒間1000コミットを捌くにはどうすればいいのか (Kaigi on Railsのフリースペースより)

先日のKaigi on Rails中の雑談として @ima1zumi さんから、RDBに対して秒間1000コミットぐらいで処理が詰まってる場合ってどうするのが良いのか、という質問を受けまして、雑談の中で色々答えてたんですが、せっかくだから記事にまとめておこうと思います。 ちょっとしたKaigi Effectって感じですね。

今回のKaigi on Railsトークの中では、 数十億のレコードを持つ5年目サービスの設計と障害解決 by KNR - Kaigi on Rails 2023 の話なんかは割と関連がありますね。ユーザーの行動履歴というのは、ユーザー数 * N * タイムスパンで増えていくレコードなので、書き込みとデータ量が爆発しがちです。トランザクションで堅牢に処理しなければいけないケースもそこまで多くないので、RDBだと書き込みに対する処理が過剰なケースが多い。実際のところこの手のデータってイベントログなんですよね。ログ収集と分析基盤で処理できる方が良いんですが、今回のpixivさんの話では履歴情報が提供機能と密接に結びついているので、その辺り難しそうな感じでしたね。 この記事では、とりあえず機能要求については置いておいて、このデータ量を捌いて永続的なデータストアに入れる時にどういう考え方をしているのか、ということを書いていきます。

ちなみに、タイトルにRailsと入ってますが、ぶっちゃけ余りRails自体とは関係が無いですw 強いて言えばこれから書くのはWeb業界の話だということです。エンプラの世界だったらOracleに何億か払うといういつもの選択肢があります。

さて、まず第一に考えておかなければいけないのは、書き込みリクエストに対してどれぐらいのレイテンシで結果を返さなければいけないのか、という点です。

書き込みリクエストから結果の反映まで一定時間待てる場合 (かつ1000tpsが瞬間的なピークの場合)

ここでそれなりに待てるなら、非同期処理に逃がして書き込みペースを調整すれば問題ないでしょう。言い換えると結果整合性が取れていれば良い場合です。

その場合に使える道具としては、AWSのSQSやKinesis、Redisなどです。

書き込みが羃等なのであればSQSが安全かつスケールが簡単なのでオススメです。SQSは基本的にat least onceなので、稀に同じキューが重複して取得されてしまう可能性があります。 実際に起きるのはかなり稀ですが、それが許容できない場合はSQSのFIFOキューを使うか、Redisで頑張るか、SQSとRedisを組み合わせて現実的に発生しない程度に確率を下げるか、という策が取れます。

Redisを活用する場合は、Railsの世界で一番楽なのはsidekiq-proを使ってワーカーの数で書き込みペースを調整するという形かと思います。proじゃないと駄目なのは、通常のsidekiqだとジョブロストの可能性が無視できないので、安全に構成できないからです。

追記: ちょっと誤解を招きそうだなと思ったので追記しておきます。これは常にsidekiq-proを使うべきという話ではありません。非同期処理のよくあるユースケースの一つとして完了通知などが挙げられますが、こういった例では稀にジョブがロストしたとしても致命的な問題にならないのでそこまで気を使わなくていい場合があります。上記の例ではまずRDBに書いて安全な記録を残す手前の部分でsidekiqを使っているので、滅多に無いことでもジョブロストしたらデータの不整合や消失に繋がります。こういった万が一のジョブロストも避けたい場合はproを使った方がいいという感じですね。

ちなみに、Kinesisは単純なキューではなくてKafkaなどに近いので、複数のサービスでそのデータが必要とかでない限りは選択の優先順位は高くありません。SQSより遥かに扱うのが難しいサービスです。

書き込みリクエストから同期処理で結果を反映しなければならない場合、もしくは定常的に1000tps以上が必要な場合

問題なのはこのケースで、これについてはある意味非常に単純な3つの道を選ぶことになります。

札束を積むか、RDBを分割するか、RDB以外のものに書き込むか。

札束を積む

ワークロードに依りますが、Auroraに金を積めば秒間1000コミットぐらいは普通に処理できます。さっき職場のRDSのメトリックを見てみましたが秒間1000〜1500コミットぐらいは1インスタンスで普通に処理できていて、まだ余裕がある感じです。インスタンスサイズはr6g.16xlargeです。これはGravitonの最大サイズですが、r6iなら32xlargeまで札束を積めるので、本当にギリギリまで引っ張れば秒間4000コミットぐらいまでなら普通に処理できる可能性が高いです。 Kaigi on Rails中の雑談では、Auroraで何とかなるか微妙なラインかもなーと答えてしまいましたが、改めて弊社の様子を見る分にはよっぽど1コミットが重くない限りは十分お金で対処可能だと思いました。もし1コミットで影響するレコードが多い、ロック競合するパターンが多い、トランザクションにおけるisolationに依存した読み込みが非常に多い、といったケースだと、Auroraにお金を積むだけでは無理な可能性はあります。

RDBを分割する

ワークロードに合わせて利用するDBをアプリケーション側で切り替えるやり方です。ゲーム系では昔からよく聞く方法ですが、これは基本的に茨の道ですね。ワークロードのパターンによって垂直分割と水平分割のやり方があります。現在のRailsはmulti DB対応が組込まれているので、昔に比べれば本体のメンテナンスに乗ることができる分やり易くなったとは言えます。 しかし、それでも複数DBは本当に必要になるまでは回避したい選択です。もし改修要求の結果複数DBに跨るトランザクションやジョインが求められた場合は即破綻しますし、アプリケーション側でそれを上手いこと回避する様に実装しても高確率でバグから逃れられないでしょう。後からデータ不整合が発覚した場合の修正コストは大抵の場合、非常に高く付きます。加えて、後から分割数を増やすのがとても大変だという落とし穴もあります。 もし本当に必要になったのなら、ドメインモデルとしっかり向き合いましょう。将来のアプリケーションの成長まで加味して慎重にモデルの区分けを行う必要があります。というか、この辺りは実質的にマイクロサービス化と同じ考え方になると思います。

RDB以外のものに書く

これは本当の所はトランザクションが必要無い場合に採用可能です。この場合は各種分散データストアの導入を検討することになります。弊社ではKafkaやCassandraを利用している箇所がありますが、レプリカ書き込みを含めて秒間100万件ぐらいの書き込みが発生しています。例えばCassandraは読み込みよりも書き込みの方がスケールさせやすい作りになっていて、クラスタの台数を増やせばこれぐらいの書き込みペースは普通に捌くことができます。弊社ではそれなりにメモリとストレージを積んだ20台ぐらいで済んでます。当然、複数台のクラスタを管理するコストが増えるし、新しいミドルウェアの知識やパフォーマンスチューニングも必要だし、複数DBと実質的に同じ問題を抱えることになるので、開発業務に与える負荷は非常に高い選択肢です。しかし、求められる書き込み量の桁が2桁とか3桁とか上がってしまうと、RDBに書くという手段ではどうにもならなくなるのでやらざるを得ないという感じですね。この分野で他に使えそうなミドルウェア/サービスは、ScyllaDB、DynamoDB、BigTable辺りでしょうか。メモリキャッシュに近い方向ならHazelcastとかIgniteとかAerospikeみたいなのもあります。

どうしてもトランザクションが必要だが書き込み件数が秒間数万件を越えるという非常にハードな現場だった場合、SpannerやCockroachDB、TiDBなどの分散SQLデーターベースの採用を検討することになるかと思います。例えばCockroachDBはマルチリージョン構成も可能なグローバルスケールのサービス展開を想定したデータベースで、PostgreSQLプロトコルと互換性があります。つまりRailsからならpgドライバで接続可能という訳ですね。 この辺りの選択肢は、とにかく技術調査力が必要です。業界全体を見ても、ここまでのスケーラビリティが求められるOLTPというのはそう多くないので、知見が全然見つからないというのはザラです。自分でアーキテクチャをちゃんと理解し、狙ったパフォーマンスが出る様にチューニングと実験をし、運用ラインを整える必要があるでしょう。お金の力があれば、エンタープライズサポートに頼ることで開発負荷を下げることはできると思います。

現実的にどうするのか

ざっとまとめさせてもらいましたが、真の意味でオンライントランザクションが必要なのかどうかで、大量のアクセスを捌く難易度は大きく変わります。一番現実的なのは「本当にそこでユーザーを待たせてはいけないのか?」という話をちゃんとすることです。5分待たせて良いんだったら全然難易度が違ってきます(非同期処理化は別の複雑さに繋がる可能性はありますが)。 あくまで自分の感覚ですが、大量のアクセスがあって本当にオンライントランザクションが要るケースはそこまで多くないと思っています。現実的な開発に合わせて仕様をコントロールするのも重要な仕事です。

そして、顧客価値のためにどうしてもオンライントランザクションが要るとなったら、まずはAWSGoogleに金を積みましょう。分かり易くキャッシュが減ってしまいますが、実際はこれが一番安くつきますし、とにかくすぐに対応可能です。

残りの手段であるRDBの分割や分散DBの採用は、ぶっちゃけ個別の地獄にそれぞれ頑張って対処するしかないんですよね。気合入れて調査して実験して計測してチューニングして社内向けのドキュメントや勉強会を準備して、とそれ相応に時間とコストがかかるので、トラフィックが追い付かない内に次の一手が打てる様に準備しましょう。実際、RDBでは色々な意味でコストが合わなくなる時というのはしばしばやってきます。そういう時のための選択肢を常に調査して、手遅れになる前に対処することが大事です。まあ、それができれば苦労しないというやつですが……。

ちなみに、Repro株式会社では、こういったことに興味があるエンジニアを募集しております。ちょっとハードなトラフィックを扱う経験が積みたいという方は、カジュアル面談からでも良いので是非ご応募ください。(現職のことも書いてるので、一応やっとかんと……)

company.repro.io