(´ワ`)

主に技術系備忘録、たまに日記。

自己紹介はプロトコル的会話

https://nnsnico.hatenablog.jp/entry/2023/04/14/222331 の続きのような内容。

仕事の作業用BGMに人の会話を欲したため、たまたまこのラジオを聞いた。

上の記事を書いたばかりなのもあって、ドンピシャに突き刺さる内容だったので考えをまとめたかった。

内容を要約すると、マッチングアプリなどで初めて会う人と会話するときにやる、当たり障りのない、所謂「薄い会話」を「プロトコル的会話」という言葉にしている。 もう少し言い方を変えると、プロトコル的会話とは「内容としては無意味に感じるが、最初の印象で変な人に思われないために、コミュニケーションにおいて仕方なくはあるものの必要な会話」と定義して話している。 プロトコルとは「決め事」の意味を持ち、コミュニケーションにおける最初の会話のことをこれと照らし合わせているということだ。

このプロトコル的会話の重要性はまさに自己紹介との重要性と大きく関係すると思っている。(内容がタイムリーだったため少し驚いた) ゆる言語学ラジオでは、シーンを初対面との会話にスポットしているが、これは自己紹介のシーンでも十分に当てはまるのではないか。 私がこれまでやっていた自己紹介というのは、プロトコル的会話における「変な人に思われないようにする」前提を壊していた、ということになる。だっていきなり知らねえスマホゲーが好きだと語るのは一般的に「当たり障り」ではないから。 そのため、最初の印象で「変な人」から脱却出来なかった私はそれに気づけないまま失敗した会話をし続けていた訳だ。

前回の記事を改めると、私が会話で必要なことはプロトコル的会話であった。と今後言いやすくなった。 どう自己紹介すべきかという部分は何も変わらない。プロトコル的会話の定義に沿って当たり障りのない紹介をして、自分が変な人でないことを証明すべきなのだ。

上記の動画を見るまで、この事象は自分にしか起こりえないことだったのではないかと思っていたが、似たような会話に違和感を感じ、それを上手に言語化してくれたゆる言語学ラジオのお二人に感謝したい…直接は言えないけど。

最後に動画の内容をきちんと理解したくて10回くらいリピートしたせいで仕事が全く捗らなかったことを反省として残しておく。

自己紹介は自分の好きなことを言っていい場所ではないという持論

時期的には新入生や新社会人が入る前か直後に投稿すべき内容ではあるが、あくまで持論なので全員が関係する内容ではない。というか誰かのためでなく、自分の戒めのために書く。

 

学校や会社など何か新しい環境に入れば自己紹介はほぼ確実にあるだろう。そのときに自分の軽い個人情報に加えて、かる〜く趣味や特技なんかも言う訳だ。

私はこの趣味・特技を言う内容について甘く見ていたことを今になって思い知らされた。

 

例えば、自分の趣味が「誰も知らねえスマホゲーム」で、普段やっていることも殆どそれくらいなので何も考えずにそれを自己紹介に加えた場合、周りはどう思うだろうか。話が膨らまないので当然「ふ〜ん」か「しらね〜」くらいだろう。

例えばと言ったが、これは私のほぼ経験談だ(若干内容や解釈が違う部分もあると思う)。私は普段から趣味とは言い難い、つまらないことばかりやりすぎて最早それくらいしか趣味として言うことがなかった。結果的に自分の自己紹介から話題が膨らむことがないまま、私はしばらく「知らねえスマホゲームをやってる奴」という認識になる訳だ。しかし、いずれこういったレッテル貼りが剥がれる日がくるまで過ごしていれば問題はなくなるため、そのように過ごしていた。が、そんなつまらん自己紹介をしてファースト・インプレッションで周りが寄り付かない人間になっていたということを客観的に認識したのは最近になってからだ。

 

このことからようやく学んだこととしては、自己紹介は本当に自分が普段やっていることをそのままクソ正直にあれこれ言っていいのではなかったということだ。

なぜなら自己紹介は一番最初の一回きりしかないからだ。一番最初に自分を明かすと、聞いている人にとっては自分がそういう人間だということを認知させる訳だ。新しい環境ではじめて会う人のファースト・インプレッションというのはとても大切だ。初動が悪くては今後の自分の身動きすら悪くなる。

今まで自分は自己紹介を「自分のことを話す場」だと思っていた。今はそれを改めるべきだと感じた。自己紹介とは「周りに自分を受け入れてもらうための場」だということを認知した。

受け入れてもらうために多少自分のことを知ってもらわないといけないよね〜くらいの感覚で当たり障りのない、共感性のあることを話すべきなのだろう。自分は天の邪鬼な性格のため、今までこの考えがあまり好きではなかったが、先程の自己紹介の定義に則則って、この性格は捨てる。

 

(昔の自分のように、共感性のない趣味しか普段からやってない人は本心のままにそのことを言うか、周りに合わせて嘘を言うしかないかもしれない。このあたりのことはまだ自分でもまだまとまった考えがついてない…)

 

この定義に沿うと主体性のない奴に思われるかもしれないが、自己紹介はその程度でいいと思う。自分の尖った趣味や特技なんて仲良くなってから信頼できるやつにだけ話せばいい。いきなり自分を全部明かしてはいけない。

 

次回いつ自分が自己紹介をするか分からないし、する予定も全くないが、今後は自分中心の考えはよして当たり障りのないことを言えるよう、よく考えて自己紹介しようと思う。

 

Kotlinのsealedキーワードで勘違いしてはいけないこと

注意

本記事はあくまで 2023/03/13 時点の備忘録ですので内容が変更する可能性があります。 今のところは個人的な備忘録で残している内容です。

概要

現時点でも「kotlin sealed class」あるいは「kotlin sealed interface」などで調べると、上位に出てくる検索結果のsealed キーワードの仕組みとして「同一ファイル内でしか継承・実装不可」といった内容で挙げる記事があるが、これは Kotlin 1.5 が安定版になった時点で「同一パッケージ内」に変更されている。

kotlinlang.org

Sealed classes can now have subclasses in all files of the same compilation unit and the same package. Previously, all subclasses had to appear in the same file.

シールドクラスは、同じコンパイルユニットを同じパッケージのすべてのファイルでサブクラスを持つことができるようになりました。以前は、すべてのサブクラスが同じファイルに存在する必要がありました。

そのため、使用している Kotlin バージョンが 1.5 以上であれば、設定と相談した上でパッケージ内に定義しておくことも可能。

余談

Scala でも同等の機能を持てるsealed キーワードがあるが、こちらは同一ファイル内に制限されている。

docs.scala-lang.org

On the flip side, exhaustivity checking requires you to define all the subtypes of the base type in the same file as the base type (otherwise, the compiler would not know what are all the possible cases).

反対に、網羅性チェックでは、基本型と同じファイルで基本型のすべてのサブタイプを定義する必要があります (そうしないと、コンパイラは考えられるすべてのケースを認識できません)。

nvim-lspのジャンプ機能をcoc.nvimの頃のものから置き換える

Zenn に投稿した以下の記事のおまけを今更書く。

zenn.dev

背景

以前まで LSP クライアントプラグインとして coc.nvim を使用していた。 その時はジャンプ先の挙動をカスタマイズするために、 VimScript を用いて独自の挙動を行うスクリプトを書いていた。 当日の記事は以下。

nnsnico.hatenablog.jp

しかし、Neovim への完全な置き換えに合わせて、 builtin LSP へ置き換えることにした。

機能・変更点

上記記事で紹介したスクリプトの主な機能は以下。

  • ジャンプ先ファイル名が同じ場合は edit コマンドで開く
  • ジャンプ先ファイル名が違う場合は split 、あるいは vsplit で開く

builtin LSP は LSP の仕様に柔軟に対応されているため、上記に加えてジャンプ先のカーソル位置も取得できるようになったため、よりリッチな動作が可能になった。

しかし、カスタマイズについて考慮する範囲は coc.nvimプラグイン以外に増えることがなかったことに対して、 builtin LSP は少し異なる。

ジャンプ先の一覧画面と画面内の動作について、 coc.nvim は全てプラグイン内の View を用いている。 一方で builtin LSP はこれを QuickFix リストで表示する。 そのため、以下 2 つに対して考慮する必要がある。

  1. 通常のジャンプ先の挙動を差し替える入口
  2. QuickFix より選択されたときの動作

Zenn の記事で書いたとおり、 vim.lsp.handlers に関数を渡すことで引数からジャンプ先の挙動をデフォルトのものから差し替えられる。 前者はこれを用いて、関数の引数から、ジャンプ先の情報を含むレスポンス( Location , LocationLink など)を受け取れるため、これでカスタマイズが可能となる。

QuickFix リストでの挙動変更はファイルタイプ別にスクリプトを読み込むように、 ftpluginafter/ftplugin で定義するのが良さそう。 QuickFix のファイルタイプは基本的に qf とされているため、この名前で作成されたファイル内にジャンプの挙動を定義する。

以上を以って、以下でこれらを作成している。

  1. 通常のジャンプ先の挙動を差し替える入口
  2. QuickFix より選択されたときの動作
  3. ウィンドウの分割処理全般

以前より記述量が増えたため、多少ややこしくなっているが、簡単に説明すると、順序としては以下の動作をしている。

  1. vim.lsp.utils.locations_to_items() でレスポンスからロケーションリストを作成する
    • レスポンスがリストでない場合、作成できないため、単一のテーブルが返ってくる場合などはリストにラップして返す
    1. のリストの個数が 1 個の場合でかつ、それが現在のファイルであればカーソルの移動のみ行う。さもなくばvsplit あるいはsplit を用いて分割で対象へ表示、カーソル移動する
    1. のリストの個数が複数である場合、ロケーションリストをセットし、lopenで QuickFix リストを表示する
      • 以降の挙動は QuickFix より選択されたときの動作 で示すファイルタイププラグイン内の処理が行う

まとめ

つらつらと書いたが、 builtin LSP で注意することは coc.nvim のように、 LSP の機能をプラグイン全てが担っている訳ではないという点のみだと思う。 いつでもカスタマイズしやすいように、スクリプトを単体で動作できるようにメンテナンスしておくことが大事である。

おまけ

QuickFix を使う場合、 nvim-bqf を用いるとジャンプ先のコードをプレビューできるため、非常に便利。 また、 nvim-bqf の README に記載されているイースターエッグを使うことで QuickFix リストのフォーマットとシンタックスハイライトをカスタマイズできるためおすすめ。

github.com

より実用的なopenコマンドでディレクトリを開く in WSL2

WSLで open のコマンドを使いたいときの方法はもうすでにいくつもある

gitlab.com

qiita.com

しかし、 open のために、しかもWSLだけのためにわざわざCLIを入れたくなかったので、上記記事に加えて個人的にもう少し手を加えた open コマンドを実装しているのでその部分だけ書いておく

if [[ $(uname -r) =~ "microsoft*" ]]; then
  # support `open` command in WSL
  function open() {
    if [ $# -eq 0 ]; then
      local WSLPATH=$(wslpath -w .)
      /mnt/c/Windows/System32/cmd.exe /c start "" "$WSLPATH" > /dev/null 2>&1
    else
      for dir in $@
      do
        local WSLPATH=$(wslpath -w $dir 2> /dev/null)
        if [ -n "$WSLPATH" ]; then
          /mnt/c/Windows/System32/cmd.exe /c start "" "$WSLPATH" > /dev/null 2>&1
        else
          print -P "%F{160}Path not found: $dir"
        fi
      done
    fi
  }
fi

以下はただの解説。

cmd.exeはフルパスにする

普段WSLを使用するなら環境変数Windowsのパスを含めないように以下のような設定を wsl.conf に入れていると思う。

[interop]
appendWindowsPath = false

こうした場合、Windowsのパスから外れるためcmd.exeは直接使用できない。そのためcmd.exeの実行はフルパスで実行している。

cmd.exe実行時の出力は無視する

WSLからのcmd.exe実行時には必ず以下が出力される

上記の現在のディレクトリで CMD.EXE を開始しました。
UNC パスはサポートされません。Windows ディレクトリを既定で使用します。

内容は書いてある通りなのだが、注意書きなのでシステム上に支障はないし実際開けるので毎回開くたびに出るのは煩わしい。 ので、 /dev/null で吐き捨てるようにしてる。

引数なしでカレントパスを開く、複数選択で複数開く

好き好きだと思うけど、カレントパスをopenするのに毎度スペースとドット打つのは面倒なので open のみでカレントパスを開くようにしている。 また、引数でパスを複数選択することで一度にエクスプローラーを開けるようにした。使ったことはいまのところはない。

startコマンドの引数に空文字列を入れておく

WSLからCドライブを参照しているディレクトリ(フォルダ)から open したい場合、引数にパスのみの指定だとコマンドプロンプトあるいはPowershellでそのパスを開いてしまう。自分の場合、エクスプローラーで開くことだけを目的としているため、 start の第1引数に空文字入れておくことでWindowsのパス上からでも open できた。

感想

URLとか入力したらブラウザから開けるようにしたら便利だと思うけど、MacとかLinuxでそういった使い方をしたことはない(MacならAlfred、WindowsならPowerToys Runでどこでも検索できる)ので自分で使う分にはエクスプローラーを開くだけでいいかな・・・

(この記事は内容がもう古いです) zinitでgit switchのcompletion(補完)を取り入れる + α

追記

この内容はおそらく情報が古いです。最新のZshを利用している場合、標準でgitのcompletionを利用できます。

github.com

zinitがリリースされた頃から、「何やら早いらしい」ということだけでとりあえずexampleを見様見真似でコピペして使っていた。 zplug を使用していた頃に比べればこれだけでも大分早かったので特に何も触らず放置していたが・・・
その後、 git の新バージョンより、 git switch が出始めた。
現在のzshの補完では switch が対応していないことに不便さを感じたのでどうにか出来ないか調べたところ、gitの公式リポジトリからcompletionを取ってくる必要があったらしい。ので、それをzinitで取り込むために改めてドキュメントを見つつ、gitの件以外にも改善してメリットを感じた部分があったのでそのメモ。

gitの公式リポジトリからcompletionだけ取る

gitの公式リポジトリ内のcompletionファイルがある位置はここ

github.com

zinitでは単一ファイルを取り込むことが出来る。

zinit wait lucid as"completion" atload"zicompinit; zicdreplay" "https://github.com/git/git/blob/master/contrib/completion/git-completion.zsh"

一応引数の説明も。

  • wait : sleep時間。秒数を指定できるが指定しないことでターボモード(バックグラウンドロード)にできる。参考: http://zdharma.org/zinit/wiki/INTRODUCTION/#turbo_mode_zsh_53
  • lucid : ロードメッセージを表示しない
  • as : "plugin" は実行用のコマンドとして使用、"completion"は補完用として使用
  • atload : ロード時をイベントハンドラに実行するコマンド。今回の場合、zicompinit; zicdreplay を実行する。
    • zicompinit: autoload compinit; compinit
    • zicdreplay: atinit'zicompinit; zicdreplay'

ただこの場合、 git-completion.zsh で読み込まれるため、 _git に名前を変更する

zinit wait lucid as"completion" atload"zicompinit; zicdreplay" mv"git-completion.zsh -> _git" "https://github.com/git/git/blob/master/contrib/completion/git-completion.zsh"

mv : "<base> -> <renamed>" のように、 -> を挟むようにインストール後の名前を変更できる

git まで打ってtab押してswitchが出ればできてるはず。

追記(2021-02-02)

git-completion.zsh を使用する場合、 git-completion.bash もインストールする必要があった。最終的には以下のようになる。

zinit wait silent lucid atclone"zstyle ':completion:*:*:git:*' script git-completion.bash" atpull"%atclone" for \
  "https://github.com/git/git/blob/master/contrib/completion/git-completion.bash"
zinit wait lucid as"completion" atload"zicompinit; zicdreplay" mv"git-completion.zsh -> _git" for \
  "https://github.com/git/git/blob/master/contrib/completion/git-completion.zsh"

単純に git-completion.bash を取ってきて、ファイルの先頭コメントにある通りにしたがってpull時にコマンド実行させるだけ。 ただこのままだと git-completion.bash は廃止された旨が毎度出力されるため、 silent を使うことで出力を出さないようにすることができる。

【おまけ】github releaseページからバイナリファイルを直接インストールする

表題の通りのことが出来るとは思わなかった。
よく fzfripgrep を使うのでこれらをインストール。まずは fzf

zinit wait lucid from"gh-r" as"program" for \
  "junegunn/fzf"

gitのときと同様にターボモードで入れるまでは同じ。少し違う点は↓

  • from : どこのサイトからクローン(ダウンロード)するか指定できる。今回は gh-r を指定することでGithub Releaseからクローンできる。
  • for : 前述の引数を使いまわしてインストールできる

ripgrep の場合、ダウンロードされたパッケージの階層が少し複雑なので少し手を加える必要がある。

ripgrep のダウンロードされたパッケージは以下のようになっている。(最新の12.1.1の場合)

ripgrep-12.1.1-x86_64-apple-darwin.tar.gz
├── complete
│  ├── _rg
│  ├── _rg.ps1
│  ├── rg.bash
│  └── rg.fish
├── COPYING
├── doc
│  ├── CHANGELOG.md
│  ├── FAQ.md
│  ├── GUIDE.md
│  └── rg.1
├── LICENSE-MIT
├── README.md
├── rg
└── UNLICENSE

これを踏まえて以下のようになる

zinit wait lucid from"gh-r" mv"ripgrep* -> ripgrep" for \
  as"program" pick"ripgrep/rg" "BurntSushi/ripgrep" \
  as"completion" pick"ripgrep/complete/_rg" atload"zicompinit; zicdreplay" "BurntSushi/ripgrep"
  • pick : ディレクトリ内のどのプログラムを使用するか指定する。

ディレクトリ階層の通り、ダウンロードするパッケージ名は常にバージョン名で変わるため

  1. mv で一意のディレクトリ名に変更する
  2. pick で使用するプログラムを指定する

といった流れでインストールできる。

まとめ・感想

zinitはgithub releasesページからバイナリファイルも直接インストールできる。
Ubuntuとかだとlatestバージョンのパッケージを取得したい場合、自分でリポジトリを取得するように設定したりしないといけなかったりして面倒だったのでzsh依存でこれだけのことをセットアップできるのは非常に良い

coc.nvimのjumpCommandをカスタマイズする

まえがき

僕は今こそVim(or neovim) + Lspを使用してコーディングをしているが、数年前まではIntelliJ IDEA Ultimateを使っていた。 よく利用していた機能の一つとしてコードの変数や関数への参照先へジャンプする機能だ。(ソースコードナビゲーションの一種) これは新しいタブで参照先を開くため、参照先と参照元を見比べてコーディングができた。また、参照先が同じファイルだったりすると同じタブ内で開かれるので無駄なタブを開かれずに済む。これは些細でありながら素晴らしいUXだった。(本当にこういった用途かはわかりませんが少なくとも僕は助かった) 一方現在利用しているcoc.nvimでもこれと同様の機能が可能。しかし、IntelliJほどUXに優れた形で制御することはできない。 今回はIntelliJに近い形にcoc.nvimの設定を見直した内容を備忘録として残す。

coc.nvimのjumpCommandの設定

デフォルトでは現在のウィンドウにそのまま定義先へジャンプする。一応これを別タブ、あるいは別ウィンドウへ移動する設定はある。以下はコードジャンプの度に vsplit して開く例。(詳しくは :help coc-config-preferences から設定を検索)

// coc-settings.json
{
    "coc.preferences.jumpCommand": "vsplit" // vsplitしてジャンプする
}

しかしこれだと参照先が同じファイルでも別ウィンドウに同じファイルを開いてしまい、非常に無駄。なぜならばVimmerはめちゃめちゃウィンドウ開くため定義先が同じファイルなのに、そのためにウィンドウを増やしたくないから・・・

本題とは違うがこれを解決するヒントはすでにissueに上がっていた。

github.com

開発者は拡張性を持たせるためか、ちゃんとカスタマイズできる術を用意してくれていた。ありがとう。

独自の関数で制御する

coc.nvimのコードジャンプの際に返ってくる値は参照先のファイル名と該当する変数・関数のカーソル位置だ。そのため、これを用いてコントロールさせることができる。具体的には以下のようになる

  • 参照先ファイル名が同じ場合は edit コマンドで開く
  • 参照先ファイル名が違う場合は split 、あるいは vsplit で開く

これらでこのように制御する

function! s:coc_code_jump(cursor, name) abort
    if a:name == @%
        execute('edit ' . a:name)
    else
        " ひとまずvsplit, tabに開かせたかったらtabeにする
        execute('vsplit ' . a:name)
    endif
    execute(a:cursor)
endfunction

command -nargs=* CocCodeJump call s:coc_code_jump(<f-args>)
// coc-settings.json
{
    "coc.preferences.jumpCommand: "CocCodeJump"
}

これをruntimepathに含めておくことで上記の仕様に沿った制御ができる。

個人的には狭い方向にvsplitされるのは困るので広い方へ自動にジャンプしてくれるように設定した。その設定は以下( s:auto_split_for_coc() )

github.com

これで幅か高さが大きい方に対してウィンドウが自動的に開かれるのでウィンドウを移動させる無駄がない。

これによりIntelliJと同様の機能を果たせた。しかもそれ以上に開く方法をウィンドウかタブか切り替えることもできる。すばら

追記(2020/07/07)

昨日の更新で引数に call cursor() を渡さなくなった。(redrawをせずにバッファから読み込むように最適化した改修による) https://github.com/neoclide/coc.nvim/commit/5d0557f13f6c03cc3ff3a54bdc08755ea9318d4f

それに伴って上記の設定は以下のように修正できる(cursorを消しただけ)。なお coc-settings.json の変更は必要ない

function! s:coc_code_jump(name) abort
    if a:name == @%
        execute('edit ' . a:name)
    else
        " ひとまずvsplit, tabに開かせたかったらtabeにする
        execute('vsplit ' . a:name)
    endif
endfunction

command -nargs=* CocCodeJump call s:coc_code_jump(<f-args>)