kbigwheelのプログラミング・ソフトウェア技術系ブログ

プログラミング・ソフトウェア技術系のことを書きます

NP困難の問題のアルゴリズムを考える その2

kbigwheel.hateblo.jp

の末尾に書いた通り、ortoolsを使おうとしている。 そのためには定式化が必要で、今見ている問題がどのタイプの問題かを判断する必要がある。

ここら辺で重大なことに気づいた。巡回サラリーマン問題などは点と点の間に距離があり、それが問題だった。しかし、今回の塗装の問題は塗る色の間に距離などない。白と黄色の距離と、白とくろの距離は同じである(なぜならエアブラシのカップの中に入れる色が何であれ、手間は変わらないからである)。

この問題、もしかしてNP困難ではないのでは?

問題を整理する

 C_1~  C_NのN個の色を塗るとする。 また、 C_a → C_b のような塗装順の制約がP個ある。 このとき、最小となるような塗装順を見つけたい。

これは、書き換えると次のような問題になる。

 C_1~  C_NのN個の記号がある。  C_1~  C_Nをそれぞれ1つ以上含んだ最短の記号列を求めよ。 ただし、 C_aより後に必ず C_bを含む必要がある、といった制約LがP個ある。

こう考えるとNP困難ではない気がする。

多角的にこの問題を考えよう。 まず、[text: L_1] ~ [text: L_P]のどれにも含まれていない記号 C_xは考慮からすべて外せる。というのは、制約に含まれる記号を制約を満たすように順序付けしたあとは、制約に含まれていない記号はその列の任意の間に好きな順序でおけるからである。先頭にあまり全てをおいても良いし、末尾でも良い。ランダムでも数字順にソートでもいが、そのどれでも塗装回数に影響しない。

となると問題は以下のように書き換えられる。

 C_1~  C_NのN個の記号がある。  C_1~  C_Nをそれぞれ1つ以上含んだ最短の記号列を求めよ。 ただし、 C_aより後に必ず C_bを含む必要がある、といった制約LがP個ある。また、 C_1~  C_Nは制約 L_1~  L_Pの中に必ず1度以上出てくる。

これを図として表現すると、NxNの行列(対角線はn/a)になる。 例えば -  C_1のあとで C_2 -  C_1のあとで C_3 -  C_2のあとで C_1

というルールは次の行列で表現できる。

先に通る点 \ 後に通る点  C_1  C_2  C_3
 C_1 n/a true true
 C_2 true n/a false
 C_3 false false n/a

この行列を持って最適化する。それが、この問題の解き方(の気がする・・・)。

もしかして、DPで行ける?

2022/08/11追記

重みがないから更に効率的なアルゴリズムがありそうと2,3日調査しながら頭をひねった結果、制約を一つずつ見ながらtreeを作っていくロジックを考えついた。

NP困難の問題のアルゴリズムを考える

から始まる問題の解き方を考える。

まずは問題としてのモデル化から入る。

モデル化

問題のモデル化のセオリーがわからないため、NP困難な問題の中で一番有名な巡回セールスマン問題との違いをまとめることから始める。

  • 距離はない (あとでつけたい気もするが一旦なしで考える)
  • 点Aより後に点Bを通る必要がある、という制約
  • なるべくたどる点の数を少なくしたい

調べると、かなり近い(というかほぼまんま)の先行研究がいくつか見つかった。

特に 順序付き巡回セールスマン問題について - Qiita はやりたいことをほぼやっている。 ただ、モデルとして循環するような制約を除外しているようなので、循環を許容して点の通過回数2回以上を許容するような問題設定が必要になる。 たぶん、

  • Sequential Ordering Problem
  • TSP (Traveling Salesman Problem ) Order Constraint

などがクエリとして良さそう。

あと[難問]複雑な制約付きの巡回セールスマン問題(TSP)もしくはジョブショッ... - Yahoo!知恵袋の人が話題に出しているJSPの問題にも似ている。この人はセールスマンが複数いる場合の問題だけど、塗装の問題はセールスマンが1人のTSP+JSP複合問題と言えるかもしれない。

プリント基板の電気検査における数理計画法に基づくシステム最適化

そこで,カメラによるアライメント撮像を考慮 した最短検査経路を求める問題が,先行順序制約付き巡回セールスマン問題の中で も特に集荷配送巡回セールスマン問題として定式化が可能であることを示し,その 上で,小規模問題に対しては,数理計画ソルバーを用いて厳密解を求解可能である ことを示している。

お、これもかなり参考になりそう。そうか、順序制約はより一般的な用途だと集荷配送と近いのか。

先行順序付き合流可能運搬経路問題に対する局所探索法 (最適化手法の深化と広がり)

これもと期待問題にかなり近いが、verhicleが複数であるのがちょっと違う。 ここは1台でいい。

いくつかキーワードとそれらしい論文は出てきているが、これと言ったものがない。というより、こちらに理解の土壌がない気もする。

おそらく、僕が求めているものは

がほぼそれなんじゃないかと思う。 前者の論文をちゃんと読めばそれで問題は解決するかも。

もろもろ考えると論文読んで実装するのはつらそう。 googleのツールとか見るに組み合わせ問題として問題を得として、その概要を学び、solverの適用方法を調べて適当なsolover(library)を使うのが最短距離っぽいとわかった。

2022/08/08

移動中勉強していたが、

という感じ。 vercelにpythonAPIをデプロイできることもあり、google製のortoolsを使うつもりでいる。

update_tri_layer(_state)を使えばタイピングがより快適になる

わかりにくいupdate_tri_layer / update_tri_layer_state関数

update_tri_layer関数はいろんなキーマップで使用されている割にその意味がわかりにくい関数だと思う。

ちょっと調べれば、この関数がだいたいPlanckキーボード発祥で、LOWERレイヤー移動キー、RAISE移動キーを同時押ししたときにADJUSTレイヤーへ移動するためのものだということはわかる。

しかし、その詳細な使い方はかなりわかりにくい。 間違った説明や適切でない使い方が散見されることからも、よくわからずコピペしている人がかなりの割合いでいそうだ。 公式ドキュメントに専用の項目があり、それなりに説明されているものの暗黙的に前提としている知識が多く、qmkについての前提知識がない状態で読んでもほとんど理解できない。 本記事を書く上で非常に参考にさせていただいた未知の階層に挑戦Tri-Layers - Qiitaの記事ですら翻訳が役に立たず読み解けなかったと書かれている1。 例えば、応用の項目で

(なぜ上書きされない?一つ目のstateが二つ目の処理で上書きされて機能しないと思うのだが・・・)

と書かれているが、これはKeymap Overviewで書かれている用語・仕組みを理解すれば謎が解ける(端的に言うとqmkのレイヤーは複数のレイヤーが同時にONになりえて、もしそうなっている場合はレイヤー番号が一番大きなものだけが有効になるため)。

なぜupdate_tri_layerは必要なのか

update_tri_layerの最大の謎は、なぜこれが必要なのか、である。

どういう意味か。update_tri_layerは

LOWERレイヤー移動キー、RAISE移動キーを同時押ししたときにADJUSTレイヤーへ移動するためのもの

と上に書いたが、これを達成するだけなら実はupdate_tri_layerは不要だからだ。 実際、僕が今使っているtreadstone48はおろかすべてのキーマップでは使用していなかった。 というのも、画像のように MO キーをLOWERレイヤー、RAISEレイヤーそれぞれに配置するだけで十分やりたいことができるためだ(最下段のスペースの左とエンターの右のキーに注目してほしい)。

これはMOキーだけではなくLTキーでも同じだ。

では、なぜupdate_tri_layerを使うのだろう。

update_tri_layerの価値

結論から書くと、update_tri_layerを使えばレイヤー変更キー周りの体験を改善することができるからだ。

どういうことか説明する。

画像のようなMOキーのみによって同時押しレイヤーを表現している場合、

順番 押したキーの位置
(Layer 0でのキー配置基準)
押された/離されたと判定されたキー
1 初期状態(何も押していない)
2 MO(1) キーを押す Layer 0の MO(1) キー
3 MO(2) キーを押す Layer 1の MO(3) キー
4 MO(1) キーを離す Layer 3の MO(1) キー (Layer 0まで透過された結果)
5 N キーを押す Layer 3の Menu キー

以上の順番で押すとLayer3の Menu キーが押された判定になる。 これは直感的には違和感がある。押しているのは MO(2) 位置のキーと N の位置のキーなので、Layer2の N 位置にある / が入力されてほしい。 なぜこんなことになるのかの詳細はコードを見てほしいが、簡単に言うと MO(1)キーを離したとしても押しっぱなしと判定されるのは Layer 1のMO(3) で、Layer 0の MO(2) が押されていると再度判定されるわけではないためである。

先程の手順にレイヤーの変化を書き加えるとこうなる。

順番 押したキーの位置
(Layer 0でのキー配置基準)
押された/離されたと判定されたキー それによるレイヤーのON/OFF変化 有効なレイヤー 最上位レイヤー(=入力判定に使われるレイヤー)
1 初期状態(何も押していない) 0 (デフォルトレイヤー) 0
2 MO(1) キーを押す Layer 0の MO(1) キー Layer 1がON 01 1
3 MO(2) キーを押す Layer 1の MO(3) キー Layer 3がON 013 3
4 MO(1) キーを離す Layer 3の MO(1) キー (Layer 0まで透過された結果) Layer 1がOFF 03 3
5 N キーを押す Layer 3の Menu キー 03 3

これに対してupdate_tri_layer (update_tri_layer_state)ベースのキーマップはこうなる。

見ての通り、Layer1と2にあった MO(3) がTransparent(透過)キーに変更されている。これにより、Layer 1の状態で MO(2) 位置のキーを押すと MO(2) 判定になる。以下に、update_tri_layerを使ったときの挙動を書く(太字の部分が使わなかった場合との差分)。

順番 押したキーの位置
(Layer 0でのキー配置基準)
押された/離されたと判定されたキー それによるレイヤーのON/OFF変化 有効なレイヤー 最上位レイヤー(=入力判定に使われるレイヤー)
1 初期状態(何も押していない) 0 (デフォルトレイヤー) 0
2 MO(1) キーを押す Layer 0の MO(1) キー Layer 1がON 01 1
3 MO(2) キーを押す Layer 1の MO(2) キー (Layer 0まで透過された結果) Layer 2がON 012 2
update_tri_layer_stateがレイヤー1と2のONを検知してレイヤー3をONにする Layer 3がON 0123 3
4 MO(1) キーを離す Layer 3の MO(1) キー (Layer 0まで透過された結果) Layer 1がOFF 023 3
update_tri_layer_stateがレイヤー1のOFFを検知してレイヤー3をONにする Layer 3がOFF 01 1
5 N キーを押す Layer 1の Menu キー 01 1

このように、言うなれば M(1) キーと M(2) キーの状態を独立して直交して扱えるため、より直感的な動作が可能になる。

果たしてこれはどの程度有効なのか

と思われるかもしれない。同時押しのレイヤー(いわゆるADJUSTレイヤー)は普段使わないものを配置しているため、上記のような状況は余り発生しないと思うだろう。しかし、実は上記のような状況はかなり簡単に発生する。

この、treadstone48で 1- というキーを入力するシーンを考える。 この場合、

  1. Layer 1でQの位置を押して 1 を入力
  2. Layer 2でSの位置を押して - を入力

となる。 このとき、理想的な打鍵順序は以下になる。

順番 押した/離したキーの位置
(Layer 0でのキー配置基準)
押されたと判定されたキー
1 MO(1) キーを押す Layer 0の MO(1) キー
2 Q キーを押す Layer 1の 1 キー
3 Q キーを離す
4 MO(1) キーを離す
5 MO(2) キーを押す Layer 0の MO(2) キー
6 S キーを押す Layer 1の - キー
7 S キーを離す
8 MO(2) キーを離す

しかし、人間はそれほど正確に動けないため、このように複雑な同時押しや順序が絡んだシークエンスは容易にミスをする。特に早く打鍵しようとするとレイヤーが切り替わるタイミングは順序が前後しやすい。上記の4と5の順番が入れ替わってしまうと、一瞬レイヤー3が有効になる。これは上の方で説明した、レイヤー3へ移動した後に片方離した状態である。

順番 押した/離したキーの位置
(Layer 0でのキー配置基準)
押されたと判定されたキー
1 MO(1) キーを押す Layer 0の MO(1) キー
2 Q キーを押す Layer 1の 1 キー
3 Q キーを離す
4 MO(2) キーを押す Layer 1の MO(3) キー
5 MO(1) キーを離す
6 S キーを押す update_tri_layerを使っていない場合: Layer 3の RGB Mode + キー
update_tri_layerを使っている 場合: Layer 1の - キー
7 S キーを離す
8 MO(2) キーを離す

実際、早く打鍵しようとすればするほどこの現象は発生してしまうため、快適かつ高速な打鍵を達成するためには update_tri_layerはとても有効ということになる。

まとめ

udpate_tri_layer(_state) の動作について詳しく見ていき、その有用性について説明した。 ただ、実際のところなくてとても不便かと言うと打鍵速度がそれほど早くなかったり、レイヤー1と2の間をコロコロ切り替わるようなことがなければそれほど困らないとも言える。 更に言うと、このupdate_tri_layerを使うには自分でファームウェアを書き換える必要があり、remapなどでは実現が難しそうに見える(未調査)。

自分が何を優先するかによって、利用するかどうかを選択しよう、というのが最終的な結論になる。


  1. この記事には感謝したい。update_tri_layerをかなり詳細に調査されており、それが僕自身のudpate_tri_layer理解につながった

sensibleキーマップv1→v2(com4table)のアップデート内容

sensible v1 com4table(sensible v2)

しばらくsensibleを使っているうちにキーマップを少し変更したので、その変更内容と理由をまとめておく。

上記のキーマップは違うキーボードのものなので比較しづらいが、概要を掴むには十分なのでそのまま比較する。

親指キーに日本語入力関連キーを直接配置

一定以上のタイピング速度では LT_2 などの条件付き分岐キーマップではどうしても暴発を避けられなかった。 そのため、親指キーには直接日本語入力関連のキーを配置した。 それによって行き場を失うAltキー、Winキーは右手外側へ移すことにした。

親指キーの並び順を修正

親指部分はMOキーとSpace / Enterの位置を逆にした。これは親指の3種(日本語入力, space / enter, レイヤー)の中でSpaceとEnterキーが最も使用頻度が高いこと、かつ、使用頻度の高いキーの左右にキーを配置したほうがホームポジションを見失わず快適だったことが理由。仮にtreadstone48のように親指キーの真ん中のキーが圧倒的に押しやすいとしても、その2つ隣の日本語入力キーは比較的頻繁に押すことになる。なので、多少無理な手の並びだとしても、3つ並んでいるうちの真ん中のキーをホームポジションとしたほうが特に日本語が入力しやすい。

追記

treadstone48のような親指のホームポジションが明らかに3つのキーのうちの端っこの場合、思っていた以上に違和感があった。このことからわかったのは、どうやら親指を2キー分動かす不安定さよりSpace / Enterがホームポジションにないことのほうが打鍵上違和感がある、ということ。

Layer 1とLayer 2を大幅整理

書き出すと概ね以下。

  • Home / End / Page Up / Page DownをLayer2へ移動させて矢印キーと合わせた
  • Home / Endなどの移動に合わせて記号入力キーを左手側へ移動 & + などShiftなしでも入力できた系キーを削除、それらはすべてShift + ベースキーへ
    • キーの順序は105キーボードでの実際の並びがベース
    • , . / などはlayer 0と重複するが、nomu30のようにその部分のキーがないものを一貫した形でサポートするためこの形に

メモ

KC_TRANSを乱用すると混乱の元になるかと思い、modifierキーの上以外は極力KC_TRANSを使わないように一度はしたが、ある程度以上の速さでタイピングしているとMOキーを押しているときにEnterやSpaceを押してスカることが頻発した。なので、基本的にKC_TRANSは使うままにしている。

最近自作キーボードのキーマップについて考えていること

一時期キーマップのことばかり考えていた時期がある。

kbigwheel.hateblo.jp

ただ、完全に煮詰まってしまってそれが自作キーボードからちょっと離れる理由の一つになってしまった。

最近、またキーマップを考えたいと思い始めたのだけど、

kbigwheel.hateblo.jp

のように如何に自動キー入力や配置変更で楽を使用かと考えていた1年以上前から少し考え方が変わった。

背景は、mimic keymapなどで思った以上にトラブルが多かったことから。 うまく使えば便利ではあるのだけど、Ctrl+Shif+なにかなど、組み合わせのときに何かと問題を引き起こしがちだった。また、複雑なキー配置も忘れやすく問題が起こったときに即座に解決できないことも多かった。 決め手となったのは会社で使っているMacを英字に変えたことで、これで日本語キーボードと英字キーボードの間を行ったり来たりせずに良くなった(すべて英字キーボードに統一できるようになった)。 こうなると切り替えが必要なのはPC / Macのキー配置間ぐらいになる。

次のcon4t4ble v2は、やるとしても長時間放置したときのIME無効化(auto_disable_ime)ぐらいでよさそうだ(記号押したときの自動ME無効化(disable_ime)や記号入力の配列を擬似的に書き換える機能(mimic_keymap)もいらない)。

qmk_firmwareにswap alt/gui ボタンがあるので、これでmacとpc用を切り替えられるならいっそIMEの自動無効化も切ってもいいかもしれない。 そこまでやれば、あとはカスタムしたい要素は

#define PERMISSIVE_HOLD
#define RETRO_TAPPING

ぐらいになる(http://oookaworks.seesaa.net/article/465349890.html)。

一通りキーボード側でいろいろ入力を工夫できないか試してみて思ったのは、ファームウェアだけで行うことには限界がある、ということだった。 mimic_keymapも自動IME OFFも言ってしまえばIME側でうまく制御できればもっとよくできる話だった。ハードウェア(ファームウェア)側でやることには、OSが切り替わったりPCを複数つないでもそれぞれで設定が不要という利点はあるものの、OS側の状態がどうやっても取得できないためいつ部不器用にならざるを得ず、それがmimic_keymapやdisable_imeで自動的にIMEをOFFにするところで問題を発生させがちだった。むしろ、その複雑さからくる問題がポータビリティや可用性を下げてしまい、本末転倒なことになりがちだった。

幸い、会社と自宅のキーボードを英字配列に統一できたため、これからはOS側の設定を英字に統一して見ようと思う。その上でmaclinuxの設定差は吸収する必要があるが、それは英字/日本語よりだいぶ小さな差分で済む。 もし、IME領域含めてまで開発する動機ができたらまたソフトウェアによる入力改善のテーマへ挑んでみようと思う。

マンションの換気ダクトと塗装ブースのホースを直結できる部品を3Dプリンタで作った

省エネ記事。

最初のアイデアAmazon | 排気ホース・集合ダクト【補強 タミヤ塗装ブース・ツインファンの2本のホースを連結して1本に (タミヤホース) | ペインティングスタンド・ブース 通販から。

しかし、そのためにはCADでモデルを作らないといけない。 Fusion 360を使うかFreeCADを使うか、つまりwindowsをやむなく使うかlinuxでも使えるソフトウェアを使うか3日悩んで、ユーザビリティに劣るとしても長期的には価値が高いと判断したFreeCADを使うことにした。 ちなみにこの判断はあとで大正解だったと感じた。確かにわかりやすくはなかったがyoutubeなどに解説動画も多くあり、やりたいことができないことはなかった。これでwindowsの呪縛から逃れられるなら多少の不便は許容できる。

f:id:den8:20220212005125j:plainf:id:den8:20220212005132j:plainf:id:den8:20220212005140j:plain
FreeCADで最初に作ったモデルの印刷。これが一発でとてもきれいに出たのでFreeCADいけるやんと自信になった

f:id:den8:20220212005147j:plainf:id:den8:20220212005154j:plain
次に換気ダクトのフック部分に引っ掛ける構造のテスト印刷。これのFreeCADでのモデル制作は大変だった。が、ここを頑張って超えたのでその後もFreeCADを続けられた。やはり3Dプリントでの成功体験が強烈なのでモチベーションが保ちやすいのは3Dプリンタの良い点

f:id:den8:20220212005211j:plainf:id:den8:20220212005204j:plain
先に印刷したやつをフックへ引っかけようとすると肩甲骨の間のところが邪魔でちゃんと引っかからなかったのでその部分を除去したモデル。これならピッタリ引っかかった。ちなみにフックのサイズはちゃんと採寸して、多少の余裕を加えてサイズを設計してある。

f:id:den8:20220212005218j:plainf:id:den8:20220212005225j:plainf:id:den8:20220212005232j:plain
1つ目と2つ目の試作の比較。左右の出っ張りの間の部分があるかないかが違う。この辺、細かく試作しながら試せるのも3Dプリンタの良い点。しかもPDCAが早い、これぐらいのパーツなら1時間以内にできる。

f:id:den8:20220212005240j:plainf:id:den8:20220212005246j:plainf:id:den8:20220212005253j:plainf:id:den8:20220212005259j:plain
ダクト径を量って一度試しに作ってみたモデルがこれ。十字の支えの一箇所がちゃんと外径へ接していない問題など発覚。また、このような単なる円柱状だとダクトホースへうまく接続できないことがわかった。さらに、設置場所の机の高さからダクトホースは斜め上方向から接続する必要があるため、この部品の間で角度を変える必要があることもわかった。こういったことも実際に試してみないとわからないのでやはりPDCAは大事。

f:id:den8:20220212005306j:plainf:id:den8:20220212005314j:plainf:id:den8:20220212005321j:plainf:id:den8:20220212005328j:plain
3Dプリンタの新しいファームウェアが公開されておりoctoprintとの相性問題が解決したらしいのでSDカードからとoctopiからで印刷結果を比較。結論、問題は解決したっぽい。また印刷時間も従来octopi経由だと長くかかっていたのが差がなくなった、気がする。今後はoctopiを常用することに。

f:id:den8:20220212005335j:plainf:id:den8:20220212005343j:plain
ダクトホースとうまく接続するため、ライフリングのようなものを刻むことに。これもまた3Dプリンタで作るの大変だった。最終的に円柱からねじを減算してモデルを作った。これはその初作だが、回転の方向が逆なのとサポートが邪魔すぎる問題点が見つかった。

f:id:den8:20220212005351j:plainf:id:den8:20220212005359j:plain
1回目の反省を生かしてネジの回転方向を逆にし、同時にサポートをなくした。サポート無しでちゃんと印刷できるか少し不安だったが問題なかった。この辺りはサポートがないとほとんどまともに印刷できないSLAタイプとは大きく異るところ。

f:id:den8:20220212005420j:plainf:id:den8:20220212005429j:plainf:id:den8:20220212005436j:plain
このあと、とても苦労してモデルができた(2つの向きの違う円柱をスムーズにつなぐのに数日かけた)。モデルができたので本印刷前に実物の数分の1のサイズで印刷したのがこれ。しつこいが、こういった試作がスケールの係数一つ変えるだけで印刷できるのは本当にすごい。この印刷結果を手に取りながらいろいろ見た結果、問題なさそうだったので本印刷へ入ることにした。

f:id:den8:20220212005442j:plainf:id:den8:20220212005450j:plain
本印刷1回目。残念ながら全体の40%ぐらいのところで印刷が途切れていた。状況を観察するとフィラメントがヘッドからすっぽ抜けていた。そうなった原因は不明。

f:id:den8:20220212005456j:plainf:id:den8:20220212005503j:plainf:id:den8:20220212005511j:plainf:id:den8:20220212005517j:plainf:id:den8:20220212005524j:plain
本印刷2回目。モデルが原因かはまだわからなかったのでモデルを変えずフィラメントをちょっと高いやつに。結果、こちらも印刷途中で途切れてしまっていた。こちらもフィラメントがすっぽ抜けていた。ここでモデルのGコードに異常がある可能性(unloadのコードがバグで混入してしまった?)を疑ったが、1回目と2回目の印刷結果を比較するとその高さ = 印刷失敗タイミングが違うことに気がついた。ということはGコードではない。モデルデータ内のGコードが原因なら必ず同じ場所で問題が発生するはずだからだ。よってモデルが原因ではないと判断した。次に疑ったのはZ軸の移動で、このモデルのような背の高いものを印刷するのが久しぶりでかつZ軸移動で激しく泣きが発生していたため、自転車のグリスを塗ってZ軸の移動をスムーズにした。もう一つ原因として考えられたのが3Dプリンタの振動で、今プリンタを廊下の棚へ設置しているため部屋への出入りのタイミングで結構振動が伝わってしまっていた可能性があった。そのため、印刷中は気を使ってなるべく振動を伝えないように廊下を移動するようにした。

f:id:den8:20220212005532j:plainf:id:den8:20220212005542j:plainf:id:den8:20220212005549j:plainf:id:den8:20220212005557j:plain
本印刷3回目。今度こそ成功。2回目までの失敗要因がZ軸だったのか振動だったのかは不明だが、たぶんZ軸のような気はした。

というわけで早速試しに設置してみたのだがとてもよかった。 かかった費用はフィラメント1KG未満で前半は安物を使っていたのでおそらく2,000円もかかっていない。 今回はできたものにも大満足だが、なによりFreeCADで工業デザインのためのCADを学べたのが本当に良かった。僕はもともとロボットコンテストで興味を持って高専に入ったのだけど結局学科は当時急速に伸びていた情報工学科を選んだ。そんなわけで昔から機械科の友達がCADを使っているのを少し羨ましいと思っていた。だから昔からの念願が一つかなった感じ。

なぜ今10年使ったzshをやめてfish shell / nushell / xonsh / elvishへ移行しようとしているか

zshをかれこれ10年ぐらい使っている。

最初のきっかけは忘れたけども、本格的に使うようになったのは2007年ぐらいに書かれた「漢のzsh」という伝説的な紹介記事によるもので僕の .zshrc には未だに 漢のzsh 4thから引用 みたいな設定がたくさんある。

【連載】漢のzsh | TECH+

そんなzsh、僕は会社でmac自宅でlinuxを使うようになってもhomeshickというツールを使って設定を同期していたのだけど近頃調子が悪い。自宅のlinuxの方は問題ないのだけど会社のmacでshellの立ち上げに5秒ぐらいかかるようになってしまった。 tmuxを使っているのでshellを立ち上げ中に元のペインへ戻って作業をしているのだけど考えてみると結構作業効率を落としている。なにか目的があって別のシェルを立ち上げているので、一時的にでも別のことをせざるを得ないということは思考が中断されている。人間の脳のコンテキストスイッチはかなり効率が悪いのでこれは良くない。

調べてみたらzsh+anyenvが不味そうなことがわかったのでそこを直そうかとも思ったんだけど、そもそもその問題を解決しても起動に1.5秒ぐらいかかる。 なんでこんな時間がかかるんだとzshプラグインマネージャーを比較し始めたあたりでもう嫌になってきた。

そうだ、fishをためそう。

fish shell

fish shellは新時代のshellとして数年前から話題になっていた。 ただ、移行に踏み切らなかった理由はいくつかあって、一つにposix標準じゃないこと、もう一つはportabilityの観点だった。

fish shellの懸念

posix標準のshellというのはだいたいsh互換のシェルという意味で、ifやwhileなどの構文がshと同じようにかけるshellだ。 fish shellはこのif for whileのような構文がshと違うことがネックだった。というのは僕のようなインフラ寄りのエンジニアにとってシェルスクリプトは日常書くので2種類の構文を使うと混同して混乱してしまう恐れがある。

もう一つのportabilityというのはログイン先のサーバなどでfishが使えないことで、数年前はまだfish shellは新しめのshellという印象だったのでログインした先のredhatcentosで使えるかが不安だった。当時は日常的に数十台のサーバへログインして作業しており、サーバによってはその中でツールをインストールして実行していたので手元のPCと出先のサーバでshellが違うことはこれまた認知負荷を余計に高める要因だった。

しかし、状況が変化した。

サーバレスの時代

オンプレからAWS初期の時代はインスタンスに直接sshしての作業は日常茶飯事だったが、Lambda, ECS, EKSへ移行していくに連れcompute nodeへログインすることはおろか踏み台サーバを使用することすらまれになってきた。 そして今の会社ではついに全面EKS / lambda移行を行い、踏み台サーバすらない状態になっている。 こうなると懸念だったポータビリティの観点はほとんどどうでもよくなる。 また、posix標準についても5年前は不慣れだったposix標準構文に十分慣れたことで混同する恐れがほとんどなくなったために問題はクリアされた。

zshからの移行先

こうして、fish shellを避ける理由がなくなったのでzshrcからfishへの設定移行をしているのだが、bigwheel/fish-castleかなり移行を進めた段階でこれはこれで気になる点がいくつか出てきた。一つは設定のポータビリティで、サーバーへはログインしなくなったものの会社と自宅PCで同じ設定は使いたいのだがfish shellはuniversal variablesという変数周りのポータビリティがそもそも設計思想になく各PCでuniversal variablesは固有の思想になっているため同期に難がある。

またC++で書いているのも気になって、令和ならgolangかrust使っているほうが将来性があるのではという気になってきた。 そこで上がった選択肢が以下

  • xonsh
  • nushell
  • elvish

特にnushellはすでに一定の信頼を得ている気がするので、これが良さそうであればfish shellを飛び越してこれにするのもありだと思っている。

追記

fish, nushell, elvishと試した結果、結局fishを使うことにした。 nushellはHistory (ctrl + r) with fzf · Issue #1616 · nushell/nushellにある通りhistoryに対して高度な検索を行えないのが痛い。 elvishは最初ctrl-aのようなショートカットキーが使えなくて、しばらく調べてreadline-binding: Readline-like Key Bindings - Elvish Shellで有効化できることはわかったのだけど、fishの哲学のout of the boxで使用できるというものが以下に価値があるかわかった & elvishは全編こんな感じで対処しないといけない匂いを感じたので結局fishに戻ってきた。