Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

IntelliJ IdeaにIdeaVimを入れてNeovimとほぼ同じ動作をさせる

JavaScalaで仕事をしていたころは毎日使っていたIntelliJですが、いつの間にかRustで仕事するようになってまったく開かなくなりました。最近はNeovimで仕事をしており、そちらの方がもはや慣れています。

ところが最近Kotlinを使う必要が出てきたので、Neovimでいつも通りセットアップしたところ、kotlin-language-serverがあまり安定的に動作してくれませんでした[*1]。たとえばリネームをかけるとエラーを吐いて死ぬので、パッチを投げるというような状況です(執筆時点ではまだマージされていません泣)。

github.com

他にも不具合を見つけていたり、そもそも定義ジャンプ(Go To Definition)が私の環境では動作していないように見えるなど、結構不具合がまだ多めです。可能な限りパッチは投げたいと思って調査していますが、喫緊必要なのでIntelliJを使わざるを得ない、というわけです。

プライベートでは今後もNeovimを使い続けるであろう手前、IntelliJのショートカットキーとの頭の切り替えはたぶん難しいです。そういうわけで、IdeaVimをなんとかハックして、Neovimの環境に近い状況にできないかと考えました。

IdeaVim

IdeaVimはVimの設定ないしはキーバインドを反映できるJetBrains製品向けのプラグインです。

github.com

初期設定で.vimrcを読み込んでくれますが、.ideavimrcというファイルがあればそちらを優先的に読み込んでくれます。.ideavimrcを用意してJetBrains向けの設定を書いておきつつ、.vimrcをsourceして手元の設定も読み込ませておく、という使い方が無難そうかなと思っています。

Action List

エディタ内での操作は基本的にはIdeaVimの初期設定でなんとか間に合います。が、たとえばIntelliJのターミナルを開きたいとか、左にくっついてるファイルファインダーを呼びたいとか、そういった操作は初期設定だけだとIntelliJのデフォルトになってしまいます。Neovimを使用していた際は、たとえば<leader> thとか、<leader> eとかで開いていたので、これと同じキーバインドを割り当てたいわけです。

調べてみると、IntelliJには「Action List」と呼ばれる設定が存在することを知りました。たとえばここに載っているリストを見ていくと、ターミナルのオープンはActivateTerminalToolWindow、ファイルファインダーのオープンはActivateProjectToolWindowといった具合にです。これに対して、Vimでのキーバインドを設定すると、オリジナルのキーバインドIntelliJ上のアクションを呼び出すことができます。

あるいは、IdeaVimに「Track Action Ids」という機能があるので、これを利用してもAction Listを知ることができます。

オンにすると、ショートカットキーで何かを動作させると右下にアクションIDが表示される。

設定してみた

いわゆるリーダーキーは「space」に割り当てています。下記のように設定してみました。

let mapleader = " "

" Key mapping
nmap gi <Action>(GotoImplementation)
nmap gr <Action>(FindUsages)
nmap <leader>fa <Action>(GotoAction)
nmap <leader>ff <Action>(SearchEverywhere)
nmap <leader>fw <Action>(FindInPath)
nmap <leader>c <Action>(CloseContent)
nmap <leader>bc <Action>(CloseAllEditorsButActive)
nmap <leader>bC <Action>(CloseAllEditors)
nmap <leader>e <Action>(ActivateProjectToolWindow)
nmap <leader>la <Action>(ShowIntentionActions)
nmap <leader>ls <Action>(ActivateStructureToolWindow)
nmap <leader>lr <Action>(RenameElement)
nmap <leader>o <Action>(EditorEscape)
nmap <leader>th <Action>(ActivateTerminalToolWindow)
nmap <leader>q <Action>(HideAllWindows)
nmap <leader>/ <Action>(CommentByLineComment)
nmap [b <Action>(PreviousTab)
nmap ]b <Action>(NextTab)
nmap u <Action>($Undo)
nmap <C-l> <Action>(NextSplitter)
nmap <C-h> <Action>(PrevSplitter)

github.com

困った点としては、ファイルファインダーを表示できる<leader> eやターミナルを表示できる<leader> thは、キーバインドをしてしまうとトグル形式にはならないようで、Neovimでは一度キーバインドを押してもう一度押すと開いて閉じるわけですが、IntelliJは2度目は認識してくれず、開いたまま閉じませんでした。これは結構困るので、<leader> qで開いているツールウィンドウを全部閉じるように設定してみました。

一旦普段開発で利用しているものはだいたい設定できたと思います。当然ですがtelescopeはないし、lazygitはターミナルをわざわざ開いて起動する必要ありです。

まとめ

とりあえずほとんど同じキーバインドで動かせるようになったので、エディタの切り替え時に混乱することも少なくなりそうです💛

ところで、kotlin-language-serverに代わる何かを実装したくなってきました。ScalaのMetalsくらいを目標にちまちまがんばりたいかもしれません。

*1:Kotlinは公式がLanguage Serverを用意していません。JetBrains製で、IntelliJなどの自社製品を使ってもらうのが一番いいはずなので、戦略上理解はできますが、できれば使用するエディタはあまり縛っては欲しくないですね。

git commit --fixupを使いましょう

発端

ポストの前提がちょっとわかりませんが、レビュー後にforce pushされると、どこに修正を入れたのかわからないケースだと仮定します。プルリクエストがまだドラフト状態でのforce pushやrebaseで困るケースはそんなにないと思うからです。

git commit --fixup

このケースではgit commit --fixupが便利です。レビューで指摘が入ったコミットに対して--fixupをかけておき、レビュワーはfixupコミットの内容を確認します。レビュワーが確認してOKが出た段階で、git rebase -i --autosquashなどを使ってfixupコミットを元コミットにrebaseします。こうすることで、最終的に見えるコミットは非常にきれいなものになります。

fixup周りのひととおりのフローは下記が参考になると思います。どのコミットに対する修正なのか紐付けされるので、レビュー指摘事項の修正に関するコミットを新たに積み上げるより、効率よくわかりやすいと思います。

qiita.com

個人的な意見ですが、「レビュー指摘事項の修正」のようなコミットはあまり積み上げても情報量が少ない関係であまり嬉しいことはなく、fixupで綺麗に整理しておくのが無難だと思います。squash & mergeしている場合は別ですが…。

fixupコミットをrebaseし忘れる問題

一点問題があるとすれば、fixup!とついたコミットをrebaseして綺麗にし忘れる問題です。これをしてしまっては、情報量のほとんどないコミットログを何個も積み上げてしまいます。

こうした問題にはCIでの対処が有効です。GitHub Actionsを利用しているようであれば、下記のアクションが利用できます。

github.com

あるいは、「そのプルリクエストの中に含まれるコミットの中に登場するfixup!メッセージを含む行をカウントし、1以上だった場合はエラーとする」といったスクリプトを用意して対処する手もあります。

追記: lazygitだとかなり楽にできます

私は普段lazygitでコミット関係を管理しているのですが、lazygitもしっかり対応しています。

コミットログを楽に整形できるlazygitの紹介 | ランサーズ(Lancers)エンジニアブログ

勉強方法について

最近よく聞かれるのですが、実際のところ答えに困ったので普段何をしているかをメモしておこうと思います。自分語りです。前提として、筆者はソフトウェアエンジニアであり、ソフトウェアエンジニアとしてどうしているかという話をしています。

学び方

学ぶチャネルは学ぶ対象に完全によります。大別するとふたつかもしれません。

  • 文字媒体(技術書やドキュメント、チュートリアル)を読んで学ぶ。
    • 「大規模言語モデル」「TypeScript」のような大きなテーマを学ぶ際は、基本的に技術書を読んでいます。
  • YouTubeなどの動画を見て学ぶ。
    • 技術書やドキュメントを読んだ上で、特定のテーマについて具体的に知りたくなったときに利用しているかもしれません。
    • 最近だと、Neovimのセットアップについてよく海外のストリーマーの動画を見ていました。
    • 書籍やドキュメントからだけではイメージをつかむのが難しいものを学ぶ際に利用できます。
    • 数学やアルゴリズム系は読んだだけではわからないことが多いので、動画をよく利用していそうです。

大きな興味関心のあるテーマがわかっているものの、具体的にどういう要素があるのかわかっていないときには技術書を利用しているようです。たとえば「大規模言語モデル」や「TypeScript」などの関心ワードはわかっているものの、具体的にそれが何で、何ができるかといったことはわかっていない、という状態のとき、技術書を最初から読んでいくのは大いに役立つと思います。

余談ですが、学び方はひとによると思います。私のように視覚優位で言語優位なひとは、おそらく書籍等で学んでいくのがもっとも効率が良いです。一方で、ひとから話を聞くのが得意な聴覚優位なひともいると思います。そうした方は、Udemyなどの講義形式のものがよいと思います。自身がどちらが得意かは、たとえば学生時代に授業を50分なり90分聞き続けられていたか、それが苦でなかったかを思い出すとよいかもしれません。参考程度にですが、私は当時はそれが苦痛で、今でも座ってひとの話を聞くのが難しいです。

加えて視覚優位聴覚優位の話もそうですし、大人になってからの学び方は、そもそも大人になると脳の使い方が変わるので、学生時代のころとは変える必要があります。実は人間の脳は25歳ごろに完成をようやく迎えると言われており*1、30歳〜50歳くらいがもっとも脳力(?)のピークにあたるのだそうです。詳しくは下記の本などがおもしろいです*2

一方で私も完全に書籍のみで学んでいるかというとそういうわけでもなく、たとえば「他人の開発環境設定が気になる」とか、「このツールのこの機能ってどうやって使ったらいいのか」といった、特定のテーマについて具体的に知りたくなった際には動画を見る傾向にあるかもしれません。最近だと下記の動画がおもしろく、Amethystというツールをそこから知って入れたりなどしました。

www.youtube.com

加えて、数学系やアルゴリズム系は残念ながら一読しただけではイメージできないことが多く、人が図解しながら説明する様子を見ると理解が進むことが多いです。そのため、こうした分野を学ぶ際には動画を比較的早い段階で活用する傾向にあるかなと思います。たとえばグラフ理論のこのシリーズは、図がわかりやすくとても直感的でよかったです。

www.youtube.com

英語で学ぶのか日本語で学ぶのかについても記しておきます。たしかに英語で普段仕事をしている関係で、そんなに英語には抵抗はないのですが、書籍というか書き言葉になるとさすがに難しいと感じることが多いです*3。結局のところ第二外国語なので無理して英語で毎回学んではいません。邦訳のものがある限りは、概念の理解は日本語でやりきることを重視しています。動画やポッドキャストなどの会話については、そんなに文法が難しくないことから英語でそのまま受容してしまっていることが多そうです。最初から第二外国語で学び切るのはたしかにすごいことですが、時間は有限なので効率を考えて母国語でまずは概念習得をし、理解した段階で第二外国語でも、くらいのスタンスです。

学ぶ際に気をつけていること

濃淡をつける

完全ないしは細かく理解する必要があるものと、そうでないものとを濃淡つけて学んでいます。学ぶこと、学びたいことは多いのですが、人生はそれに比較するととても短いのです…。

  • それを「使いたい」ので、細かく理解する必要があるもの。
    • 仕事で直近使う技術や概念などは、理解の解像度を高めておく必要がある。
    • 書籍を読みながらNotionに読書ノートを取るなどして、できるだけ用語やその技術の概念構造が記憶に残るようにしている。
    • もしくは、その技術の学習に本当に時間を費やしたいと思っている場合。
  • そうでないもの。知識だけ欲しいもの。
    • 単に興味があるだけのもの。最近だとLLMとか。
    • 脳内に知識の地図(インデックス)を作ることを目的とする。

大事なことは濃淡をつけて学ぶことです。すべてを完全に理解していくのが理想的ではありますが、時間が無限にないとほとんど不可能でしょう。モチベーションの維持の問題もあります。社会人の場合、日々忙しく過ごしていると関心が次々別のことに移っていきます。それ自体はなんら悪いことではありません。そうした状況の中で、自身がとれる現実最適解を探すとよいと思います。

私の場合は仕事等で使用したいものは基本的にきちんと腰を据えて学ぶようにしています。仕事等で使う以上は高い理解の解像度でもって接する必要があると考えているためです。理解の解像度が高いとは、人に聞かれた際にその技術に関して一通り正しく漏れなく語れるようになることをいうかもしれません。たとえばその技術が取り組んでいる課題領域に対する理解そのものから始まり、複雑な内部構造をもつものはその内部構造への理解などといったところでしょうか。

逆に「そうではないもの」を学ぶ際は、あくまで知識の地図を拡充できればいいやくらいの気持ちで取り組んでいることが多いです。最近よい記事がありましたが、脳内にその領域のインデックスを作ることを目的としています。専門用語の名前とそれらの関係性くらいは頭に入っているが、世界史の問題集にあったような一問一答に答えられる程度であって、詳しく聞かれても答えられはしない、くらいのレベル感です。

levtech.jp

そもそも「そうでないもの」を学ぼうとする人は、世間的には体感そんなに多くなく、これはプラスアルファの話だと思います。他に趣味ややることがあるのであればそちらが優先されて然るべきです。私がたまたま好奇心が強い性格で、なんでも知りたいと思ってしまうために、このルートがあるだけなのです。できなくともなんら気にする必要はないのではないか、と私は個人的には思っています。

身体知を大事にする

知識の習得は思った以上に身体的な活動だと思います。チャンスがあれば身体を動かしながら学ぶようにします。ただここでは、ウォーキングやジムの電動サイクルに乗りながら書籍を読めといっているのではなく、たとえばコードの写経であるとか、手元に紙とペンを用意し、マインドマップなどの図に書き起こしながら読むということを「身体的」と言っています。

これについてはさまざまな研究があると思うので詳細は割愛しますが、人間は身体を通じて、周辺環境と関わりながら知識を習得していくようです。最近子育てをずっとしている関係で、子どもを見ているととくにそれを感じます。言語習得の瞬間などが際たる例でしょう。座って読んだだけですべてを理解し記憶できれば最も省エネで理想的ではありますが、一度見ただけですべてを瞬時に記憶できる人などでない限り、まず不可能でしょう。そこで役に立つのが身体的な経験を伴う学習であると私は考えています。ひとは、身体的な経験などを通じてちょっとずつ知識を内面化していくのだと思います。

そういうわけで、結構しっかり写経したり、複雑で理解が難しい箇所が出てきた際は図を起こします。Notionに読書ノートをとるのも、やはり身体的な知識の習得を意識してのものです。

時間がかかることを前提とする

成果や結論が出るのを急がない、ともいうかもしれません。

そもそも知識は1時間や2時間で身につくものではありませんし、積み重ねなければ増えていくことはありません。ある分野に関して、自分より理解があり知識があると感じられるひとたちは、そこにかけた時間が多いのです。時間をかけているからこそ、そこに辿り着いているのです。

また、1時間や2時間学んだとしてその場で学んだことを理解できていなくとも落ち込む必要はないと思います。たしか下記に示す本で昔読んだんですが、知識は(個人差はありますが)半年くらいかけてだんだん消化されていき、いつか「わかった!」が来ることがあります。そもそも知識や理解は環境や身体的経験と共に構築されるものですから、人生経験を積んでいるうちに理解が深まることが多々あるのです。

それくらいの時間軸で取り組むようにしています。

まとめ

何かを学ぶ際に何を考えているかをまとめてみました。なおいうまでもありませんが、常にやっているわけでもないです。「使いたい」側であっても、少し学んでみて「やっぱり深く学ばなくてもいいからとにかく使いたい」と思い、さらっと読んで済ませるだけのこともあります。まずまずそういう傾向にあるかな、という話を記しました。

*1:人間の脳を25歳くらいまで柔軟な状態に保っておくことで、環境の変化に長い期間対応できるようにし他の生物に対して競争優位に立つことで、生存してきたのではないかと言われています。ちなみにですが、精神疾患が15歳ごろ〜25歳ごろに多いのも、これが原因なのではないかと考えられているようです。詳しくは ヒトの発達の謎を解く (ちくま新書) | 明和 政子 |本 | 通販 | Amazon など。

*2:タイトルは死ぬほど怪しそうなんですが、最新の脳科学の知見などは知らない素人の意見ですが、そこまでひどい内容ではないと思います。むしろ、よく整理されており普通にいい本です。どうでもいいですけど、医師の書く本って、なんでプロフィールや実績をやたら強調するんでしょうかね。医学の知識の権威性はそこにあるわけではなく、普通に論文の引用数などで決まってくると思うんですが、それをベースとした本がなさすぎると思います。

*3:私の英語のレベルはCambridgeだとC1くらいです。アカデミックな内容を読みこなすにはまだちょっと遠い、みたいなレベル感ですかね。

年初でアップデートした開発環境の話

いくつか記事を読んで、もう少しGitならびにGitHubの操作周りを便利にしたいと思ったので、いくつかアップデートをしてみました。追加したのは次のとおりです。

  • ghqの導入と、リポジトリfzfで探せるようにした。
  • ghコマンドをちょっと覚えた。
  • Orbstackをbrew installで追加できるようにした。
  • tmuxで新しいウィンドウを開いた際に直前で使用していたディレクトリパスで開くようにした。
  • miseを入れた。

ghqの導入と、リポジトリfzfで探せるようにした

これまで私の開発環境ではリポジトリ間の移動が結構大変だなと思っていました。ghqそれ自体にはいろいろ用途があるようなのですが、今のところはghq listを便利に使っています。

公式にはpecoと組み合わせてリポジトリ一覧を取得しつつ、カーソルを動かした先でEnterすると該当するリポジトリに移動できるというものが載っています。私はfzfの方を使っているので、fzf用にカスタマイズしました。このgistを参考にしました

github.com

exaを使っているので、ついでにexaが呼び出されてカーソルを動かした先のプレビューがツリーで表示されるようにしています。Ctrl+gを押すと出てきます。

実のところ仕事の方のPCではtmuxのウィンドウを仕事用のリポジトリに割り当てて行き来してるだけなので、仕事の方は困りません。時々ウィンドウに用意しているリポジトリ以外に移動する必要が出てきた時に活用しています。

ghコマンドをちょっと覚えた

全然使っていませんでしたがghqをBrewfileに登録する際に近くにghというのがいたので、せっかくだから使ってみようかと思った次第です。普段はlazygitでほとんど管理しているのでlazygitと組み合わせて使おうかなと思っています。lazygitのショートカットキーを割り当てて使うといいよというアドバイスを見かけたので、どこかで整理したいと思っています。

とりあえずPull Requestをターミナルを出ずに作成できるのが便利です。gh create prですね。ただPull Requestを作成する際、descriptionを書く時にNanoが開いてしまって困ったので、そこだけ微調整しました。Vimが開いて欲しかったので、

gh config set editor vim

で調整できました。

Orbstackをbrew installで追加できるようにした。

少し前まではたしか手元にダウンロードしかダメだったと思うんですが、Docker Desktopが重すぎて無理と思いOrbstackをインストールしようとしたところ、brew installできるようになっているのを見つけました。やったね!

tmuxで新しいウィンドウを開いた際に直前で使用していたディレクトリパスで開くようにした。

地味に不便だったため。この記事を参考にしながら修正しました

github.com

あと、水平分割は-で、垂直分割は|でできるようにしておきました。直感的だったので。

miseを入れた。

miseを導入してみました。少し前にrtxからリネームされたツールです。さまざまなenvをこれひとつで管理できるようになります。細かい機能はまだまだ開発途上な気はしますが、すでにある程度使える状態にはあると思います。

github.com

miseのおかげで結構な数のbrew installを飛ばせていることがわかるdiffです↓

github.com

そのほか

あとはこの記事を見ながら、xhを追加したりしました。exaのフォーク版ezaが出てるんですね。どこかで乗り換えなければと思いつつ、さまざまに設定したエイリアスを切り替えるのが面倒で重い腰が上がっていません…

zenn.dev

dieselでbatch upsertをするには

Rustのdieselでbatch upsertをやる方法について、検索してもなかなか苦労したのですごく簡単にメモしておきます。バックエンドはPostgresを想定しています。MySQLでもこれができるかはわかりません。

結論

diesel::insert_into(...).values(...).on_conflict(...).do_update().set(...)でいける。

excluded句を入れたい場合

たとえばcolumn_aというカラムをexcludedしたいとします。最後のsetの呼び出しのところで、.set(column_a.eq(excluded(column_a)))とすれば実装できます。複数カラムの場合、setにタプルを投げ込むと指定できます。たとえばcolumn_acolumn_bcolumn_cに対してexcludedしたい場合、

diesel::insert_into(...)
                .values(...)
                .on_conflict(...)
                .do_update()
                .set((column_a.eq(excluded(column_a)), column_b.eq(excluded(column_b), column_c.eq(excluded(column_c))))

のようにです。

複数カラム指定を楽にする

ただカラムの数が増えてくるとだんだん間違えます。そこで、このタプルの生成はマクロに任せてしまうといいと思います。次のようなマクロを書きます。

macro_rules! excluded {
    ( $( $column:expr ),* ) => {{
        use diesel::{upsert::excluded, ExpressionMethods};
        ($( ($column.eq(excluded($column))) ),*)
    }}
}

これを先ほどのsetに投げ込みます。

diesel::insert_into(...)
               .values(...)
                 .on_conflict(...)
                .do_update()
                .set(excluded! {
                    column_a,
                    column_b,
                    column_c
                })

これで、あとはコードが展開されて勝手にexcludedを含むタプルが生成されます。結構便利だと思うので、困ったなと思ったらぜひ使ってみてください。

『詳解Rustアトミック操作とロック』(Rust Atomics and Locks)

昨年買っていたんですが、年末年始の時間を使って少し読めました。

著者はRustコンパイラにコントリビューションをしたことがあれば誰でも知っているかもしれない、Mara Bos氏です。

ちなみにですが、原著は下記サイトで無料でも読むことができます。

marabos.nl

書籍は下記です。

なおこの記事内で「本書」と明記する場合、それは『詳解Rustアトミック操作とロック』を指します。また、「筆者」は私自身のことであり、「著者」はMara Bos氏のことです。

内容のメモ

読んだ内容のうち、印象に残ったり初見だったものをメモしておきます。

1章

1章は主にはRustの並行性を理解するために必要なツールと概念が説明されます。具体的には、スレッド、Mutex、スレッドの安全性、内部可変性などです。TRPLよりさらに深く説明されるため、なるほどそういう意味があったのかと思う箇所がいくつかありました。

Mutexのpoisoning周りの説明と、ならびにMutexGuardの説明はとくに勉強になりました。MutexGuardの説明の中で、次のコードは一見すると結果が同じに見えるものの、実はボローチェッカーの挙動が異なるせいで挙動が変わるらしいというのを知りました。

if let Some(item) = list.lock().unwrap().pop() {
    process_item(item);
}

if list.lock().unwrap().pop() == Some(1) {
    do_something();
}

後ろのコードから説明しますが、後者のコードは後続の処理内で何かリストから取り出した要素を使用したりしません。返す値は常にboolであり、何もそこから借用しません(コピーセマンティクスです)。したがって所有権を気にする必要がなく、また一時変数の生存期間を気にする必要がありません。これにより、一時変数のガードはif文が実行される前にドロップされます。つまり、ロックは条件式を抜けると解除されます。

一方前者のコードでは、一時変数itemは(後続のブロック内で)借用の可能性があるため、生存期間をif文の最後まで延長する必要があります。仮に借用が一度も起こらなかったとしても、ボローチェッカーはドロップするタイミングや順番には影響を与えず、生存期間のチェックだけは行なってしまいます。これにより借用した場合と同じように見かけ上は動作してしまうらしいです。ロックは解除されません。

上記のコードは、次のように一時変数をifの外で作っておいて、それを利用するようにすると良いです。生存期間を意図通り細かく区切れるため、意図したスコープを抜けるとドロップが発生します。結果Mutexのロックを解除することができます。

let item = list.lock().unwrap().pop();
if let Some(item) = item {
    process_item(item);
}

以前X上の議論で、似たようなものがありました。そのときの説明では、これはパターンマッチに脱糖されるのでそこからスコープを考えてみると、生存期間がパターンマッチの腕の中までと断定できるのではという話がありました。が、これは微妙に説明不足だったと思っていて、理由としてはとくに私が手元でHIRで脱糖の状況を確認したところ、if letは必ずしもmatchに変換されているわけではなさそうだったためでした。そういうわけで、脱糖の観点からの説明は難しいのではないかと当時は考えていました。アナロジーとしては正しいんですけどね。そしてこの節を読んで、実はこのように借用とそれにまつわる生存期間からの説明が妥当だったということがわかりました。

2章

RustのAtomicではじまる機能についての解説です。Javaでは結構使ったことがあって馴染みがあるんですが、Rustの方はあまり使ったことがなかったなと読みながら思いました。いい勉強になりました。アトミック操作を使うとこういう機能を実装できるよ、という例がいくつか示されており、実アプリケーションでの利用を考えながら学べてよい構成になっていると感じました。

3章

ところで、この章を読んでいる最中にtokioのIssueを眺めていたところ、自作ツールを使ってtokioの特定の箇所に対して検査をかけてみた結果、SeqCstではなくAcquire/Releaseで十分な箇所を見つけている人を見つけました。ちょうど学んでいる内容がリアルタイムでやってくるとテンションが上がりますね。

github.com

Mara先生の教訓、

SeqCstは警告だと思った方がいい。SeqCstをどこかで見かけることがあったら、何か複雑なことを行なっているか、それを書いたプログラマがメモリオーダリングに関する想定を十分な時間をかけて解析していない、ということだ。いずれも、特に注意しなければならないサインだ。

を見た気がしました。もちろん、tokioではSeqCstが必要な箇所にはこのようにコメントがされていて、よく検討されているかどうかはすぐにわかるようになっていそうに思いました。

4章、5章

スピンロックやチャネルをフルスクラッチします。手を動かしつつ先ほど3章で出てきたメモリオーダリングを復習する章になっていると思います。

4章ではガードと呼ばれる実装パターンが紹介されています。これはRustを触っているとたびたび出てくる実装パターンなように思います。ガードは何か内部的に状態をもっておき、ドロップされるまでそれを維持する役割を果たします。ロックの実装と相性がよく、主にはロックで用いられているように見受けられますが、それ以外の用途でもたびたび見かけます

6章

この章ではArcを実装しつつ、メモリオーダリングについての理解をさらに深めていきます。ArcはRustの標準ライブラリに存在しており比較的使う回数は多いものの、内部で何をしているかはなんとなくしか把握していないことが多いと思います。この章をWeak対応版まで進めると、実質Rustの標準ライブラリのそれとほとんど同等になります。

Miri

ところでこの章を読んでいて作ったArcに対するテストが実装されるのですが、そのあとに「miriを実行してみるといいだろう」という話があります。これをやってみたので補足しておきます。

MiriはRustの中間表現であるMIRを実行するインタープリタです。unsafeコードをチェックする際に利用できるツールで、未定義動作の発生をチェックできます。MiriはコンパイラのMIRを用いて、ネイティブなプロセッサ命令にコンパイルせず、型やライフタイムなどの情報がまだ残っている状態でコードを実行・解釈させるツールです。インタプリタ方式なのでコンパイル後の実行と比較するとかなり実行時間はかかりますが、その文未定義動作になりうるようなさまざまなミスを検出できます。データ競合の検出も実験的ながらもサポートされており、これを使うとメモリオーダリングの問題も検出することができるそうです。

Miriは意外に簡単に実行できます。次のようにしてMiriを手元にインストールして、既存のテストに対してMiriを実行するだけです。

$ rustup +nightly component add miri
$ cargo +nightly miri test

ちなみにですが、今回実装したArcのテストではとくにMiriによる不具合の検出はありませんでした。unsafeなコードを書く際にはMiriもきっちり実行しておきたいものです。はじめて使いましたが便利でした。余談ですが、RustでLinked Listを実装してみる有名なチュートリアルにもMiriで検証する節があり、こちらは実際に不具合を検出させて修正させる形をとっているようです。一度このチュートリアルもこなしておきたいなと思いました。

Loom

こうした並行処理にまつわるテストにもうひとつ使えるツールとして、tokioが提供するLoomというプロジェクトがあります。[*1]Loomは、C11のメモリモデルで有効な実行の構成に応じて実現可能な組み合わせを変えながら、テストを何度も実行してくれるツールです。同時に状態数を削減しつつ組合せ爆発を回避しているようです。並行処理のテストは往々にしてこうした組み合わせを網羅するのが難しいわけですが、Loomを使うとこの点をある程度緩和できるというわけです。

私も今回はじめて使ってみたので使い方が合っているか謎ですが、tokioのテストケースなどを見る限り、次のように使いこなすことができそうでした。下記は本書で一度目に書くWeak実装前のテストコードをLoom化してみたものです。

#[cfg(test)]
mod tests {
 // 他のテスト用の余計なimportsも含まれるかも
    use loom::lazy_static::Lazy;
    use loom::sync::atomic::AtomicUsize;
    use loom::sync::atomic::Ordering::{Acquire, Relaxed, Release, SeqCst};
    use loom::thread;
    use std::marker::PhantomData;

    use crate::Arc;

    #[test]
    fn test() {
        // static NUM_DROPS: AtomicUsize = AtomicUsize::new(0);
        static NUM_DROPS: Lazy<AtomicUsize> = Lazy {
            init: || AtomicUsize::new(0),
            _p: PhantomData,
        };

        struct DetectDrop;

        impl Drop for DetectDrop {
            fn drop(&mut self) {
                NUM_DROPS.get().fetch_add(1, Relaxed);
            }
        }

        loom::model(|| {
            // Create two Arcs sharing an object containing a string
            // and a DetectDrop, to detect when it's dropped.
            let x = Arc::new(("hello", DetectDrop));
            let y = x.clone();

            // Send x to another thread, and use it there.
            let t = thread::spawn(move || {
                assert_eq!(x.0, "hello");
            });

            // In parallel, y should still be usable here.
            assert_eq!(y.0, "hello");

            // Wait for the thread to finish.
            t.join().unwrap();

            // One Arc, x, should be dropped by now.
            // We still have y, so the object shouldn't have been dropped yet.
            assert_eq!(NUM_DROPS.get().load(Relaxed), 0);

            // Drop the remaining `Arc`.
            drop(y);

            // Now that `y` is dropped too,
            // the object should've been dropped.
            assert_eq!(NUM_DROPS.get().load(Relaxed), 1);
        });
    }

// 他のテストが続く

Arcは今回自分たちで作ったテスト対象なので、これはそのまま利用します。それ以外のたとえばメモリオーダリングを指定するOrderingや、スレッドを生成するthread::spawnの類はすべて、loomが提供するものに置き換えられています。あんまり中身がよくわかっていませんが、このloomで始まるものは内部的にトラッキングされていて、loomの並行処理実行時(loom::model)にエラー等が検出された場合、このトラッキング情報を追ってデバッグできるように作られているようです。

7章

キャッシュ周りを一旦読みましたが、この章はあとで戻ってきてじっくり読みたいと思っています。

8章

futexに関する解説が載っていました。著者はMutexの性能向上に貢献した張本人ですが、この件について調べていた際にfutexについていつか深く学んでおかないとと思っていました。ちょうどよかったです。

9章

8章までで出てきた概念やライブラリを用いて、Mutex、条件変数、リーダ・ライタ・ロックを実装します。どれもステップバイステップで少しずつ想定される問題を解決しながら実装していくので、非常にためになります。

たとえばMutexの場合、まず最小の実装で一旦実装し切ります。その後、wait/wakeに関連するシステムコールの呼び出し回数が問題になりうることが説明され、呼び出し回数を削減するためには他の待機スレッドがあるかどうかの判定を入れれば良いことが説明されます。最後にベンチマーカーを実装して実際に動かしてみつつ、Mutexのベンチマーカーを作る難しさと簡単に実装したベンチマーキングの結果の解説が挟まれるといった方式です。私はこれが非常によかったと思っています。

作られるMutexはRust標準ライブラリのそれとよく似た構成になっています。Rust標準のMutexのロック部分を作るのにはLinuxならfutex、それ以外のOSであれば相当するシステムコールが必要になりますが、それはatomic-waitというクレートを使うことで結構簡単に実装できました。

リーダ・ライタ・ロックを実装する際にはwriter starvation問題まできちんとステップバイステップで解消していきます。

10章

これまでの章で拾いきれなかった話題について解説されます。セマフォやロックフリー連結リストなどです。解説を読みながら実装してみるとおもしろそうですが、まだ試せてはいません。

感想

私は普段RustでWebバックエンドのアプリケーションを実装しています。RustでWebバックエンドを実装していると、本書で紹介があったようなMutexArcなどを利用しはするものの、中で利用されているツールや概念について使用することはほとんどありません。

また、こうした機能を深く理解しているかというとそうでもありません(Arcは後述する通り一度ある記事を読み込んでいてちょっと知ってたものの)。表面的には何をやっているかはもちろん把握していますが、メモリオーダリングの話などは詳しく把握しているかというとそうでもないのが実情です。こういう場合、最適化を本気でやろうとすると困るわけなのですが、もっともたとえばAWS上でインスタンスサイズをスッとあげれば問題が解決してしまうことが多く、突っ込んで最適化するよりも札束で殴る会社の方が多いのではないでしょうか。それがあるべき姿なのかというと、もちろんケースバイケースだと思いますが。

そして、細かい生存期間や所有権、借用について気にすることはほとんどありません。せいぜい長い生存期間を持つもので、アプリケーションのローカルに用意するインメモリキャッシュくらいでしょうか。基本的にはほとんどの生存期間はHTTPリクエストが送られてから、HTTPレスポンスが返されるまでとなります。したがってその程度の生存期間では、ドロップの順番等はほとんど気にすることはありません。また内部可変性についても、データベースをはじめとしたミドルウェアにほとんど丸投げしてるため意識することもないでしょう(ただたとえば、書き込み頻度の高いインメモリキャッシュを持っている場合は別ですが)。

そうした意味では、ほとんど見ることのなかった世界を覗けたという意味でよい読書体験だったと思います。技術を使うことに精一杯で、裏側に控える議論をあまり深ぼっていなかったなと改めて気付かされました。読後、Rustと並行処理への理解度がまた一段あがったと感じました。

私が行なったようにtokioを探索しながら読み進めるとおもしろいかもしれません。本書で登場した内容はtokioでもそのままよく議論されている内容であることが多いようです。tokioやRust本体にコントリビューションする際によい糸口になってくれるはずです。そうしたOSSへのコントリビューションを考えている方は手元に置いておいてもよいのではないでしょうか。

著者も書籍の最後の方で説明しているように、Rustの並行処理に関する文献や資料は思ったよりありません。私の知る限りでは、後ほど紹介する『並行処理プログラミング入門』と未邦訳の『Rust for Rustaceans』10章くらいでしょうか。文法入門レベルのものはいくらでも存在するのですが、それを使ってどう実装を組み上げていけばよいかに関する教材や資料が不足しているのは著者の指摘の通りだと思います。

ところで、今回紹介されたような話題について、並行処理そのものにもう少し絞ってキャッチアップしたいと思いました。何か定番の教科書や資料などあったら教えてください🙌🏻

日本語での別の資料

この話題は実は日本語圏でも結構いい資料がいくつかあり、あわせて読むと理解が深まるかもしれません。

ひとつはqnighyさんの連載で、Arcを切り口に本書で扱う話題に入っていく流れになっています。私は実は本書の前半の内容はだいたい既知だったわけですが、それはこの記事を読み込んでいたからかなと思いました。

qiita.com

もうひとつは『並行処理プログラミング入門』という書籍です。この書籍でもやはり同じくらいのレイヤーの低いところから一気に解説がなされます。それだけでなく、Rustの難しいポイントのひとつであるasync/awaitや、Futureに関連する章もあり非常に網羅的だと思います。あわせて読むと理解が深まるかもしれません。

*1:余談ですが「loom」は織機のことです。無数の糸(thread)を束ねるもの、というイメージでしょうか。

2023年、読んで印象に残った本

あけましておめでとうございます。年がもう明けてしまいましたが、2023年に読んでよかった本について簡単に書いていこうと思います。noteで書いていましたが、こちらのブログをしっかり使わないといろいろもったいなと思ったので、技術に関係ない話題ではありますがこちらに書いていきます。

技術書

昨年は子育てに加え、そもそも技術書の執筆と翻訳を行なっていたこともありほとんど時間を取れませんでした。が、何冊か読んだので紹介しておきます。

単体テストの考え方/使い方

2023年に読んで一番よかった一冊かもしれません。テスト周りはわりとチームのエンジニアによって考え方が違うことが多いように思います。たとえば、モックをするべきかすべきでないか、あるいは単体テストでモックするか、インテグレーションテストでモックするかなどは、大きく考え方が分かれます。考え方が分かれる場合、さまざまな意見を整理して採り入れつつも、最終的には何かしらのチーム全体のマニフェスト(方針)を策定する必要があると思います。その際、この本を参考にしながら取捨選択すると、議論を空中戦にさせずに済むなと感じたのが一番大きな感想です。

また書籍内では「どういったテストが不要か」を議論していたのもよかったです。テストコードも結局のところ成果物でありメンテナンス対象となります。コードを書かされるものの得られる成果が少ないテストはできるだけなくしておいた方が、メンテナンスする対象を減らせてよいといえます。たとえば読み込みのみでほとんど複雑なビジネスロジックを持たないようなテストは、リグレッションの防止等の得られるリターンが少ない割にメンテナンステストが大きくなるだけで嬉しくない、などです。

フロントエンド開発のためのセキュリティ入門 知らなかったでは済まされない脆弱性対策の必須知識

たぶん産休育休中だったと思うけれど、フロントエンドのセキュリティを全然知らないのはまずいかなと昔から思っていたのを思い出して読みました。フロントエンドはそれなりに仕事上関わる機会があるものの、社内向けの管理画面の実装などが多く、なかなか本格的にやる機会のない分野でした。そういう人でも十分理解できるように書かれているいい本だと思います。

とくにこれまでの現場で迷いがちだったのは、ログイン周りの情報をどこに保存しておくべきかという問題でした。個人的にはCookieにHttpOnly属性をつけて入れておくのがよいと漠然と思っていましたが、これがなぜよいのかについて詳しく理由を知ることができました。このような日頃から疑問に思っていたことは一通り学べたという意味でよかった一冊だと思います。

私のように普段はバックエンドに軸足があるものの、ときどきフロントエンドも触る必要があるようなソフトウェアエンジニアにおすすめです。

プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで

基礎的な文法はある程度知っていて、実際になんとなく書いているものの、体系的に言語仕様を理解していないなと思っていたランキング1位(個人)TypeScriptを学ぶ際に使った一冊です。これも育休中に読みました。著者が言語処理系に詳しそうな印象をもっており、そこを期待して購入した一冊です。もちろん期待通りでした。

この本のすばらしいところは、TypeScriptの仕様に関連する言葉をふわっと利用せずきちんと定義し、説明し切るところです。入門書を書いたことのある方ならもしかすると共感していただけるかもしれませんが、そうした本を書いていると、どうしても初学者のうちは理解の妨げになるかもしれないと思い、説明を少し緩めにして誤魔化すといったことを時折することがあると思います。本書ではそういったことはせず、読者の理解力を信じてきちんと説明し切っているように見受けられました。

技術書でないもの

月に1、2冊は読んでいたと思いますが、とくに印象に残ったものを紹介します。

サピエンス減少 縮減する未来の課題を探る

投資をしていると未来の人口動態というのはとても気になるものです。労働者人口が伸び続ける限りは、基本的には経済は成長を続けるだろうという予測がこれまでの歴史から立つためです。というわけで人口動態については、私は結構キャッチアップするようにしています。

これはさまざまな本や予測で論じられていることですが、世界人口は2060年ごろにピークを迎えてその後減少に転じるという予測が今のところ有力なようです。日本ではすでに人口増加率が落ちてきて人口減少に転じていますが、これが世界の多くの地域で起こる未来が来る、ということです。つまり、全世界株式インデックスの長期保有は私たちが生きているうちは大丈夫そうなのですが、私たちの子どもの世代まで有効な投資戦略かどうかはちょっとわからないかもしれない、ということです。人口減少は資本主義の歴史上初めて到達する局面であり、単純に何が起こるかよくわかりません。

そしてそれより衝撃的なのが、一度人口が減り始めると結構がんばらないと人口は戻らず、300年もしないうちにそのまま一気に100分の1程度にまで減少してしまうというモデルです。人口は一度減少しだすとどうやら戻すのが難しいようで、場合によってはそのまま大幅に自然減少してしまうもののようです。車に乗っていてスピードを結構出しているとブレーキをしたとしても少し効くまでにラグがあると思うのですが、あれと似たようなことが人口減少でも起きると言うわけです。

人口減少は地域に関係なく起こりうる未来であることも示されます。平均寿命の伸びが関係します。平均寿命が伸びると、女性の再生産期間の残存数が伸びます。たとえば平均寿命が25歳の地域と50歳の地域とでは、35歳を適齢期の上限とすると単純に考えても10年ほど子を産める期間が延びることになります。これにより、結婚出生のタイミングが自由化します。自由化すると、たとえば私たちの世代が今まさに悩んでいるように、キャリアと出産の天秤をかけはじめるようになります。あるいは、恋愛も自由化しているので相手を吟味して数年費やします(私たちの世代が今マッチングアプリでやっていることだと思います)。私たちの世代では30歳前後で産むのが最適解っぽく感じられるので、そのくらいで子どもを産むわけですが、そうすると適齢期の上限問題に行き当たります。25歳で第一子を産むのと、30歳で第一子を産むのとでは、第二子、第三子を産むためのハードルが全く異なります。これにより、生涯に産む子どもの数が減ります。

この本もやはり似たような話が書かれていた記憶があり、おすすめです。

ネガティヴ・ケイパビリティで生きる

タイトルのネガティブ・ケイパビリティというのは最近はやりつつある概念だと思います。一言で説明するのは難しいですが、「答えを出すことを急がずに一旦そのまま事実を受け止めしばらく抱えて考えておく」みたいなイメージでしょうか。ここ最近は、どうもスパンと物事を言い切る人に人気が集まりがちですが、物事というのはそもそもどういう側面から見るかや、解釈する側がどういう立場かによって味方が変わるものであり、そう簡単にスパンと峻別できるものでもないです。この辺りを意識しつつさまざまな事象に向き合う姿勢がネガティブ・ケイパビリティというものだと思います。

タイトルはあくまで標語みたいなもので、本書の中身は哲学者や公共政策学者が、ここ数年の現代社会についてさまざまにああでもないこうでもないと対話する本になっています。哲学の役割のひとつに、その時代に生きる人々がなんとなく思ってはいるものの言語化できていない考えや思想をスパンと言語化していくというものがあるのですが(だから哲学を形容して、ミネルヴァの梟は迫り来る黄昏に向かって飛び立つ、などと言われる)、これをまさに果たせている本だと思います。

この本を知ったのは著者のうちのひとりの本を読んだからでした。やはり同様に現代社会に対して感じていることを言葉にしてくれる一冊になっていて、ある種のカタルシスを読後得られると思います。

2050年の世界 見えない未来の考え方

未来予測はあまりあてにはならないので参考程度に読みました。個人的には未来予測よりも最初の章の方の現状の各国や各地域の分析のところをおもしろく読みました。人口が伸びる地域伸びない地域があり、金融危機はヨーロッパで起こる可能性が少しありそうで、暗号資産は価格変動が大きすぎる関係で基軸通貨にはなれず、民主主義はあまり人気のない政体であり、テクノロジーは引き続き人類のさまざまな問題を解決し続けるといったところでしょうか。ただ、いかんせん扱われる国や地域、話題が広いため、おもしろかったです。

訂正可能性の哲学

東浩紀氏の新作です。まだ前半しか読んでませんが印象に残っているので記しておきます。「家族」という概念を手がかりに訂正可能性を軸とした共同体という新たな発見をし、これを政治社会に適用してみるみたいな構成なように今のところは見えています。

最初の方はプラトンの国家を参考に家族がどう扱われていたかを見つつ、そこから閉ざされた社会と開かれた社会の対立軸を見出します。プラトンの思想には若干全体主義的要素があるのは有名な話ですが、そこを批判したことでさらに有名なポパーを引きます。が、ポパーは実は微妙な勘違いを元にプラトンを読んでいるという読みをします。ここからプラトンから脈々と続く家族の否定という思想史は、実は家族を完全に否定し切ることができていないということになってしまいました。家族を出た人間関係は作れず、家族の枠の中で議論する必要があるのでは、というところから、今度はウィドゲンシュタインとクワインの議論を引いていきます。この辺りから訂正可能性という概念を導き、これを政治社会に適用できないかを検討するために、アーレントやローティの力を借りていくみたいな構図でしょうか。

後半はルソーの一般意思や、人工知能民主主義あたりが議論されるようです。楽しみです。

GitLabに学ぶ 世界最先端のリモート組織のつくりかた ドキュメントの活用でオフィスなしでも最大の成果を出すグローバル企業のしくみ

GitLab社の働き方や文化について解説されている一冊です。GitLab社はフルリモートワークかつ分散オフィスを上手に回していることで有名です。どうやったらそうした組織を上手に回せるか、そのための文化や制度について網羅的に解説されています。私も同様にフルリモートかつ分散拠点をもつアメリカの会社に勤務していますが、ここまできっちり制度化されてはおらず、非常に興味深く読みました。また、日系企業に勤める方にとっては外資の会社の回し方を知るいい機会になると思います。

全体的な感想としては組織運営をかなり「科学している」と感じました。たとえばおもしろかったのは、人間関係に専門性を持つチームがいて、人間関係に関連するトラブルを一定に引き受けることがあるらしい、などです。また、再現性が高まるようなオンボーディングの設計や、さまざまに用意されたガイドラインの存在などがそうした印象をより強めました(ところで本書のサブタイトルは結構微妙で、ドキュメントが中心というわけではなく、私はこうした科学的手法のとり入れに伴う再現性の担保による属人化の防止が大きなポイントなのかなと思っています)。

本書の魅力はGitLabの紹介にとどまらないと思っています。おそらく著者の方による解説だと思いますが、GitLabの施策がなぜ有効なのかを、最近の経営論や組織論の学説を踏まえて解説してくれるところがすばらしいです。仮にGitLabの紹介だけにとどまっていたとしたら一社だけの事例でしょ、で済んでしまった話が、こうしたアカデミックな議論も踏まえて解説されることにより、実は他の組織でも重要な話であり応用可能なのだ、ということに気づけると思います。

2024年の展望

技術書でない本は引き続き、自分のおもしろそうと思った本を読んでいると思います。技術書の方は、今年買ったもののキャッチアップできなかったChatGPTやLLM関連の書籍をいくつか読みたいと思っています。具体的には下記です。

ゼロから作るシリーズの生成AI編も楽しみですね。