Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

ワンライナーでバージョン文字列を比較する

結論: localeCompare を使う

a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'})

参考: https://stackoverflow.com/a/65687141

localeCompare は IE11 でも使えるので、使えない心配はほぼありません。

しかし、locale とついているためブラウザ実装依存ではないか? と不安になりますよね。今回はそこを調べてみました。

まあ、結論から言うと 実装依存っぽいので自身のサポート環境でしっかり検証してから使う必要があります。

localeCompare は何をするメソッドなのか

まず localeCompare とはどういうメソッドなのか、ecmascript の仕様書 から引用しつつ説明します。

This method returns a Number other than NaN representing the result of an implementation-defined locale-sensitive String comparison of the this value (converted to a String S) with that (converted to a String thatValue).

つまり、localeCompare は呼び出し元(this)と引数(b)を比較して NaN 以外の数値を返すメソッドだそうです。

文字列の順序は実装に依存しています。また、返す値も negative か 0、plus という大雑把な具合にしか決まってなさそうです。仕様を読みましたが、それ以上の説明はありません。結構昔に導入されてブラウザ間の差異が激しくて仕様がフワッとしている…?

環境差異は大丈夫なのか

以下のスクリプトを Node v16.0.0、Chrome112.0.5615.137、Safari 15.6.1 で検証してみましたが全て true になりました。なので おそらく問題ないです (IE で確認したいけど手元の環境にない…)。false になる環境あったら教えてください。

console.log(
  '0.0.0'.localeCompare('0.0.1', undefined, { numeric: true, sensitivity: 'base' }) < 0 &&
  '0.0.1'.localeCompare('0.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.1'.localeCompare('1.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.1.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.2.3', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('1.2.3', undefined, { numeric: true, sensitivity: 'base' }) < 0 &&
  '10.0.0'.localeCompare('9.1.2', undefined, { numeric: true, sensitivity: 'base' }) >0
)

↑これくらい満たせていれば流石に大丈夫だと思います。

配列でも使える

this は string へ変換されるので、 apply を使えば配列に対しても使えます。

↓こんな感じ

''.localeCompare.apply([1, 0, 0], [[0, 0, 1], undefined, { numeric: true }])

// bind を使うとこんな感じ
''.localeCompare.bind([1, 0, 0])([0, 0, 1], undefined, { numeric: true })

// bind operator が使えるとこんな感じ
[1, 0, 0]::''.localeCompare([0, 0, 1], undefined, { numeric: true })

dein.vim から packer.nvim に乗り換える

lua でファイル管理したいなぁ…という気分になったので乗り換えることにしました。今のところそんなに違いは感じてないです

(まだ書き換えてる途中なので以下の記述はミスってるかもしれないです)

migration guide

そもそも、 packer.nvim でインストールする方法が書いてあるプラグインが多いので、まず GitHub リポジトリをみましょう。 dein.vim についてはインストール方法書いてないのに packer.nvim はあるとか結構あります。

次に、Lua-guide - Neovim docs を読みましょう。neovim 公式が lua の書き方を解説していてわかりやすいです。日本語資料もあります。この2つを読めば大体かけます。

それでも足りなかった時用に、以下に tips をまとめます

  • hook_add は setup に置き換える
  • hook_source は config に置き換える
  • nnoremap は vim.keymap.set('n', 'key', 'command') に変える
  • let g:hoge =vim.g.hoge = に直す
    • どうしようもない場合は vim.cmd [[ let g:hoge = xxx ]] でいける(他の場合も最悪 vim.cmd [[ ]] で囲めば OK)
  • nmap は vim.keymap.set の第四引数に {remap = true} を渡す以外は nnoremap と同じ
  • on_cmd は cmd に直す
  • on_event は event に直す

たとえば deinlazy.toml にこう書いてあったとする

[[plugins]]
repo = 'nvim-telescope/telescope.nvim'
on_cmd = ['Telescope']
hook_add = '''
nnoremap <leader>ff <cmd>Telescope find_files<cr>
nnoremap <leader>fg <cmd>Telescope live_grep<cr>
nnoremap <leader>fb <cmd>Telescope buffers<cr>
nnoremap <leader>; <cmd>Telescope resume<cr>
'''
lua_source = '''
require'telescope'.setup({
  defaults = {
    mappings = {
      n = {
        q = "close",
      },
    },
  },
})
'''

これはこうなる

return require('packer').startup(function(use)
  use {
    'nvim-telescope/telescope.nvim',
    cmd = {'Telescope'},
    setup = function()
      vim.keymap.set('n', '<leader>ff', ':<C-u>Telescope find_files<CR>')
      vim.keymap.set('n', '<leader>fg', ':<C-u>Telescope live_grep<CR>')
      vim.keymap.set('n', '<leader>fb', ':<C-u>Telescope buffers<CR>')
      vim.keymap.set('n', '<leader>;', ':<C-u>Telescope resume<CR>')
    end,
    config = function()
      require'telescope'.setup{
        defaults = {
          mappings = {
            n = {
              q = "close",
            },
          },
        },
      }
    end,
  }
end)

lua としてそのままシンタックスハイライトとか使えるようになるので便利 (多分 toml の中の lua をシンタックスハイライトつける方法もあると思うんですが、やっぱ lua は lua ファイルに書いたほうが便利だと思う)

Misskey のウィジェットに複数ソースを扱う RSS リーダーが欲しい

公式の RSS リーダーウィジェットだと単一の RSS しか購読できないので、複数の RSS を読めるウィジェットを実装しました。

RSS リーダーウィジェットの追加方法

手っ取り早くウィジェットが欲しい人は以下の手順でどうぞ

  1. AiScript App ウィジェットを追加
  2. AiScript App の設定を開く
  3. script 欄に 複数ソース RSS リーダー のソースを貼り付ける

以上です

RSS リーダーの使い方

まず「購読する RSS を設定する」を押します。そして、RSS の URL を入力します。以上です。ページをリロードすると反映されます。

AiScript 書くときに知ってると便利なもの

AiScript を初めて書いたんですが、かなり苦戦しました。なので参考になったドキュメントとかまとめておきます。

この辺はメチャクチャ読みました。あと、AiScript の playground もめっちゃ使いました。

それから、Misskey Play を使いこなせるとだいぶ開発が早くなります。

Misskey Play を使ったデバッグ方法

  1. Misskey Play で Play を追加する
  2. ソースを編集する
  3. 保存する
  4. 表示する
  5. 更新日時が(たった今)になるまでリロードを連打する (重要)
  6. Play を押して検証する

特に更新日時が(たった今)になるまでリロード連打するのが大事です。なぜか Misskey Play はキャッシュが効いてるっぽくて最新の状態になっているとは限りません(リロードをかけると過去のバージョンに戻ったりする)。なので最新のものが出てくるまでリロードを連打する必要があります。

記事のアウトラインだけ考えて ChatGPT に記事本文を書いてもらう

以下のアウトラインに沿って記事本文を考えてください

- まずアウトラインを考える
- 肉付けしてもらう
- 修正をする
- という記事がChatGPT によって生成されていたっていうオチにしたい

こんにちは、ChatGPTです。今回は、記事のアウトラインを考え、肉付けし、修正するというプロセスについてお話ししましょう。

まずアウトラインを考える

まず、記事を書く前にアウトラインを考えることが重要です。アウトラインは、記事の骨格となり、書くべき内容を整理するのに役立ちます。また、アウトラインには、記事の構成や流れを決める役割もあります。

肉付けしてもらう

次に、アウトラインを元に記事を肉付けします。この段階では、アウトラインに書いた内容を詳しく説明し、具体的な例やデータを挙げることが大切です。また、記事のタイトルや見出しも決めます。

修正をする

そして、肉付けした記事を修正します。文章の流れや表現、文法などをチェックし、読みやすく、分かりやすい文章に仕上げます。また、誤字や脱字の修正も行います。

という記事がChatGPT によって生成されていたっていうオチにしたい

以上が、記事を書く際の基本的なプロセスです。しかし、今回の記事は、実はChatGPTが自動生成したものなのです。アウトラインから肉付け、修正まで、全て人工知能によって行われたのです。

このように、人工知能は文章の生成にも利用されており、今後ますます進化していくことが期待されています。しかし、それでも人間には及ばない部分もあるため、人工知能と人間が協力して、より良い記事を作り出すことが大切です。

以上、記事のアウトラインを考え、肉付けし、修正するプロセスについてお話ししました。実は、この記事もChatGPTが自動生成したものでした。人工知能の進化に目が離せませんね。

うーん、そんなに上手くいってないな…

循環参照がある配列で flat を呼び出すとどんどん長くなる

何に使えるかは全然わからないですが発見したのでメモ。

まず循環参照を持った配列を定義します。

const a = [];
a[0] = a;
a[1] = 0;

これの flat を呼ぶと面白いことが起きます。

console.log(a.flat()); // [ <ref *1> [ [Circular *1], 0 ], 0, 0 ]
console.log(a.flat().flat()); // [ <ref *1> [ [Circular *1], 0 ], 0, 0, 0 ]
console.log(a.flat().flat().flat()); // [ <ref *1> [ [Circular *1], 0 ], 0, 0, 0, 0 ]

なんと、flat を呼び出すごとに配列の末尾に 0 が追加されていきます。ということは a.flat().flat()/* ... */.flat().length - 2 というふうにすれば flat を呼び出した回数がわかります。

…こんなテクニック、いつ使うんですかね?

余談

余談ですが、a[0] = 0; a[1] = a; とすると配列の先頭に 0 が追加されていきます。

console.log(a.flat()); // [ 0, 0, <ref *1> [ 0, [Circular *1] ] ]
console.log(a.flat().flat()); // [ 0, 0, 0, <ref *1> [ 0, [Circular *1] ] ]
console.log(a.flat().flat().flat()); // [ 0, 0, 0, 0, <ref *1> [ 0, [Circular *1] ] ]

さらに余談として、初期状態を長さ 3 にする(a[0] = a; a[1] = a[2] = 0;) と、flat を呼ぶたびに配列の長さが2ずつ増えます。

頑張ればフィボナッチ数列を取得できそうですが、だからなんなんですかね