@ledsun blog

無味の味は佳境に入らざればすなわち知れず

アーキテクトの教科書

大吉祥寺.pm - connpass にて著者の米久保 剛さんの講演を見ました。 何かの縁だと思って買いました。

どんな本?

これからアーキテクトを目指す人向けに「アーキテクトにはこういう分野のスキルを身につける必要がある」と示してくれる本でした。 ITエンジニアにとっての基本情報技術者試験のような存在です。 基本情報技術者試験との違いは、上流工程に重点を置いて抽象度の高い内容を扱っています。 例えば、2進数やコンピュータの仕組みのようなローレベルな話は出てきません。

読むと何が得られそう?

全体を概観する本です。 個々のスキルの獲得と支援する本ではありません。 若手が読めば、これから伸ばして行きたいスキルが見つかるかもしれません。 ベテランが読めば、自分が気づいていなかった、あるいは軽視していた視点が見つかるかもしれません。

アーキテクチャ設計では「非機能要求を機能要求より重視する」そうです。 この視点は私になかったものです。 どちらかというと、機能要求が主で、非機能要求は添え物と考えていました。 「非機能要求は添え物ではあるけど、見落とすとあとで痛め見るから忘れないように気をつけるもの」として意識していました。 これはおそらく僕の意識が、アーキテクチャ設計よりアプリケーション設計に偏っているために起きているように思います。 このように、普段の開発の中で知らず知らずのうちに軽視しているものに気がつけるかもしれません。

個人的な面白ポイント

あと、個人的に面白かったのは 4+1 architectural view model - Wikipedia の名前です。 アーキテクチャを見る視点を次の5つで使い分けるアイデアです。

  1. 論理ビュー
  2. プロセスビュー
  3. 開発ビュー
  4. 物理ビュー
  5. シナリオ

なんとなくやっていた行為にちゃんと名前がついていること。 それが1995年に提唱されていたことが面白かったです。

お隣の書評

ruby.wasmがビルドできない

ruby.wasmのビルドが通らなくなったので、環境を最新にします。

cd vendor/jco/
git reset --hard
cd ../..
git submodule update

します。 この手順が要るかはよくわかっていません。 雰囲気でやっています。

bin/setup に成功するまで

bin/setupを実行すると次のエラーが出ます。

error[E0463]: can't find crate for `core`
  |
  = note: the `wasm32-wasip1` target may not be installed
  = help: consider downloading the target with `rustup target add wasm32-wasip1`

For more information about this error, try `rustc --explain E0463`.
error: could not compile `cfg-if` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...
Error: command exited with non-zero code `cargo build --workspace --target wasm32-wasip1`: 101

次のコマンドを実行します。

rustup update
rustup target add wasm32-wasip1`

bin/setupが成功するようになります。

rake npm:ruby-head-wasm-wasi には成功しない

rake npm:ruby-head-wasm-wasiを実行すると失敗します。

Rubyのビルド済み環境にリセットします。 rake build:download_prebuiltを実行します。

rake npm:ruby-head-wasm-wasiを実行すると失敗します。 次のようなエラーが出ます。

  ==> make -j8 install DESTDIR\=/home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full/install
clang-16clang-16clang-16: : : warning: warning: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
argument unused during compilation: '-shared' [-Wunused-command-line-argument]argument unused during compilation: '-shared' [-Wunused-command-line-argument]

clang-16: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
wasm-ld: error: enc/cp949.o: undefined symbol: rb_enc_register
...
rake aborted!
Command failed with status (1): [RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm]
/home/ledsun/ruby.wasm/rakelib/packaging.rake:83:in 'block (5 levels) in <top (required)>'
/home/ledsun/ruby.wasm/rakelib/packaging.rake:69:in 'Dir.chdir'
/home/ledsun/ruby.wasm/rakelib/packaging.rake:69:in 'block (4 levels) in <top (required)>'
/home/ledsun/ruby.wasm/rakelib/packaging.rake:123:in 'block (3 levels) in <top (required)>'
Tasks: TOP => npm:ruby-head-wasm-wasi:build => npm:ruby-head-wasm-wasi:ruby
(See full trace by running task with --trace)

どうも RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm を実行した時にエラーが起きているようです。

ruby.component.wasmを作ろうとしてリンクに失敗しているように思います。

vender/jcoをリセット

jcoのupdateが良くないのかもしれません。 念のため初期状態に戻します。

rm -rf venber/jco
git submodule update --init

bin/setupを実行します。

`RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm に成功しない

RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm を実行します。 エラーが起きます。

make[1]: Leaving directory '/home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full'
transdb.h unchanged
clang-16clang-16clang-16: : : warning: warning: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
argument unused during compilation: '-shared' [-Wunused-command-line-argument]
argument unused during compilation: '-shared' [-Wunused-command-line-argument]
clang-16: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
wasm-ld: error: enc/cesu_8.o: undefined symbol: rb_enc_register
wasm-ld: error: enc/cesu_8.o: undefined symbol: OnigEncAsciiToLowerCaseTable
...
clang-16: error: linker command failed with exit code 1 (use -v to see invocation)
make[1]: *** [enc.mk:407: .ext/wasm32-wasi/enc/emacs_mule.so] Error 1
make: *** [uncommon.mk:1041: enc] Error 2
make: *** Waiting for unfinished jobs....
Try running with `rake --verbose` for more complete output.
bundler: failed to load command: /home/ledsun/ruby.wasm/exe/rbwasm (/home/ledsun/ruby.wasm/exe/rbwasm)
/home/ledsun/ruby.wasm/lib/ruby_wasm/build/executor.rb:77:in 'RubyWasm::BuildExecutor#system': Command failed with status (2): 'make' '-j8' 'install' 'DESTDIR=/home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full/install' (RuntimeError)
        from /home/ledsun/ruby.wasm/lib/ruby_wasm/build/product/crossruby.rb:225:in 'RubyWasm::CrossRubyProduct#build'

エラーは大きくは変わっていません。

makeで失敗している

次のコマンドでエラーが再現できるようです。

cd build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full
make install

もう少し絞れそうです。

cd /home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full/ext/cgi/escape
make
linking shared-object cgi/escape.so
clang-16: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
wasm-ld: error: escape.o: undefined symbol: rb_ext_ractor_safe
wasm-ld: error: escape.o: undefined symbol: rb_intern2
wasm-ld: error: escape.o: undefined symbol: rb_cObject
wasm-ld: error: escape.o: undefined symbol: rb_define_class
wasm-ld: error: escape.o: undefined symbol: rb_define_module_under
wasm-ld: error: escape.o: undefined symbol: rb_define_module_under
wasm-ld: error: escape.o: undefined symbol: rb_define_alias
wasm-ld: error: escape.o: undefined symbol: rb_define_alias
wasm-ld: error: escape.o: undefined symbol: rb_prepend_module
wasm-ld: error: escape.o: undefined symbol: rb_extend_object
wasm-ld: error: escape.o: undefined symbol: rb_string_value
wasm-ld: error: escape.o: undefined symbol: rb_enc_get
wasm-ld: error: escape.o: undefined symbol: rb_enc_dummy_p
wasm-ld: error: escape.o: undefined symbol: rb_call_super
wasm-ld: error: escape.o: undefined symbol: rb_string_value
wasm-ld: error: escape.o: undefined symbol: rb_enc_get
wasm-ld: error: escape.o: undefined symbol: rb_enc_dummy_p
wasm-ld: error: escape.o: undefined symbol: rb_enc_get
wasm-ld: error: escape.o: undefined symbol: ruby_scan_digits
wasm-ld: error: escape.o: undefined symbol: ruby_scan_digits
wasm-ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)
clang-16: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [Makefile:274: ../../../.ext/wasm32-wasi/cgi/escape.so] Error 1

soファイルをwasm上でダイナミックリンクするための変換処理が上手く行ってなさそうです。 しかし、どうすれば解消できるのかはわかりません。

docker composeコマンドのcompose spec準拠はcompose-goモジュールを使って実現されている

WARN[0000] /home/ledsun/pubdictionaries/docker-compose.yml: version is obsolete

というワーニングを追いかけたら 「docker composeコマンドのcompose spec準拠はcompose-goモジュールを使って実現されている」ことに気がつきました。 その記録です。

警告に出会う

docker compose コマンドを実行したら次のように警告がでました。

docker composeコマンドで警告が出ているスクリーンショット

WARN[0000] /home/ledsun/pubdictionaries/docker-compose.yml: version is obsolete

言っていることは簡単です。 対応は docker-compose .yml ファイルの version フィールドを消せばよいだけです。 でも、一次情報を確認しておきたいです。

長いissueコメントの謎

github.com

docker compose コマンドの v2.25.0 から出るようになったみたいです。 コメントのやりとりをみると

How will docker-compose.yml files for versions 2.x and 3.x be distinguished without the version tag?

Version 2と3を区別しなくていいの?

The current file format is more of a "descendant" of the 2.x series and the 3.x series is, again, only relevant for docker stack, where 2.x had no ... relevance.

docker stackしか関係なくて、docker stackはvesion 3しか扱わない。

なるほど、納得できます。 が、なんかコメントがすごく長く続いています。 飛ばし読みすると「急にログに警告が出てきてびっくりした」人がたくさん居るみたいです。 なぜでしょう?

リリースノートには記載がない

確かに、Release v2.25.0 · docker/compose · GitHub には、この警告がでることについて書いてありません。 それはびっくりする人もいそうです。 僕も、リリースノートに関連情報が書かれていないと「本当に消して良いのか」不安を感じます。

リリースノートには記載がない理由

前提として

Docker Compose V2で変わったdocker-compose.ymlの書き方

Docker Compose V2はCompose Spec[1]に準拠している

そうです。

2024年4月の version-patch by aevesdocker · Pull Request #489 · compose-spec/compose-spec · GitHub で、Composer Spec上で version フィールドが obsolete になりました。 docker comopse v2.25.0 は、3月にリリースされています。 未来の情報はリリースノートに書けません。

なぜdocker comopse v2.25.0に version フィールドの obsolete が反映されているのか?

https://github.com/docker/compose/compare/v2.24.7...v2.25.0#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6L9 を見ると github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0 を参照していたのが、github.com/compose-spec/compose-go/v2 v2.0.0に変わっています。 rcが外れています。

Release v2.0.0 · compose-spec/compose-go · GitHub を見ると

warn user version is obsolete by @ndeloof in #575

があります。 warn user `version` is obsolete by ndeloof · Pull Request #575 · compose-spec/compose-go · GitHub です。 この変更とリリースは3月に行われています。 面白いことに、compose-specより先にcompose-goが修正されていたようです。

まとめ

docker composeから見るとcopomse-goのRCを取っただけです。 以前からある「compose specに準拠する」ポリシーは変わっていません。 docker composeにとっての大きな変更ではなさそうです。

また、copomse-goの変更を全部みてリリースノートに書くのも不毛に感じます。

なるほど「compose specに準拠する」の実現方法が「copomse-goを使う」であることを知らないと、混乱しそうです。 僕の中のメンタルモデル(docker compose の理解)が更新されました。 1つ賢くなれたようです。

参考

ブラウザでruby.wasmのJS.evalしたときのスコープはなんなのか?

事の発端

tmtms さんが ruby-jp slack に面白いコードを投稿していました。

JS.eval <<~EOS
  A = 123
  class B { }
  console.log(A)  //=> 123
  console.log(B)  //=> class B {}
EOS

begin
  JS.eval <<~EOS
    console.log(A)  //=> 123
    console.log(B)
  EOS
rescue => e
  p e  #=> #<JS::Error: ReferenceError: B is not defined>
end

上段の JS.eval で定義されたAは下段の JS.eval から参照できます。 ところがBはできません。

このコードの面白い点は

A = 123
class B { }

この辺りがすごくRubyっぽくて、JavaScriptだってわかっていても、脳が勝手にRubyと解釈してしまうところです。 Rubyだと、AとBは、どちらもグローバルスコープに宣言されます。

コードの動き

Aはグローバル変数

冷静にJavaScript脳で解釈するとA = 1235グローバル変数*1の定義です。

var - JavaScript | MDN

厳格モードでない場合は、スコープチェインで宣言されている同名の変数がない場合は、グローバルオブジェクト上にその名前のプロパティを作成しようとしていると仮定して、非修飾識別子に代入することになります。

この説明も難しいです。 が、要するにA = 1235window.A = 1235と解釈されます。

Bはローカル変数

class B { }はクラス宣言です

class - JavaScript | MDN

クラス式と同様、クラス宣言の内部は厳格モードで実行されます。

こちらはグローバルスコープでは宣言されません。

つまり

Aは下段の JS.eval から参照できますが、Bはできません。

JS.eval のスコープは?

ということは、JS.eval はグローバルスコープではなくて、なんらかのスコープを持っています。 もしスコープをもっていなければ、Bもグローバル変数として宣言されます。 下段の JS.eval から参照できるはずです。

ruby.wasmのJS.evalソースコードを見てみましょう。

https://github.com/ruby/ruby.wasm/blob/8e3731b3bf901c9c05a16ce385d93c6a61ec3dbe/packages/npm-packages/ruby-wasm-wasi/src/vm.ts#L232-L234

evalJs: wrapTry((code) => {
    return toJSAbiValue(Function(code)());
}),

アロー関数のなかで、Function関数で関数を作成し実行しています。 アロー関数はスコープをつくります。 JS.evalは、グローバルスコープではなく、この関数のスコープで実行されます。 その結果、クラス宣言で作成されたローカル変数Bは、他のJS.evalから参照できません。

他のJS.evalから参照したければ、window.B = class {}のようにグローバル変数として宣言します。

おまけ

なぜJS.evalは、eval()関数ではなく、Function関数を使うのでしょうか? tompngさんに教えてもらいました。

function evalJS(code){eval(code)}
evalJS('console.log(code)') // => "console.log(code)"

evalを実行するコンテクストのローカル変数(ここではcode)が参照できてしまいます。

function evalJS(code){Function(code)()}
evalJS('console.log(code)') // => 正しく ReferenceError: code is not defined になる

(´・∀・`)ヘー

RubyJavaScriptの狭間の世界、楽しいです。

*1:雑にグローバル変数と呼んでいますが、より厳密にはwindowオブジェクトのプロパティです。

ベースの音の立ち上がりに関する所感

ここで言うベースはエレキベースとかウッドベースとか呼ばれる楽器のことです。 また、素人が適当に感想を述べているだけで、特に正確性がある内容ではありません。

ベースは音の立ち上がりが遅い楽器のようです。 弦の振動が安定するまでは、ベースに期待しているブンブーンという音に聞こえないのだと思います。 ベースの弦はギターより長くて太いです。 その分、弦の振動が安定するまで時間が掛かるのでしょう。

どれくらい遅いかというと100~200msだと思います。 根拠は「知覚はできるが明確には分離できない」くらいのふわっとしたものです。 数値は完全な与太話です。 太鼓は「叩く」イメージで叩くと、音とタイミングがズレます。 これは太鼓の音が出るのが、皮を押した時でなく、離した時だからと思います。 「押して離す」イメージで叩くとズレが減ります。 この遅れが300msくらいと考えています。 雑にその半分くらいの感覚です。

この微妙な音の立ち上がりの遅さがベースの演奏を難しくしています。 ドラムの音に指やピックで弦を弾くタイミングで合わせると100~200msズレるのです。 ということは、100~200ms早く弾けばいいのですが、人間は指の運動としては、この精度では上手く制御できません。 どうするかと言うと、音を聞きます。なんかいい感じの音が出るタイミングで弾きます。 説明になっていませんがたぶんそうです。

これはプロのミュージシャンにとっても難しいようです。同じミュージシャンが同じ曲をやっても音が合ってるときと、合っていない時があります。音が合っている時は、腰でノれますが、合っていない時は音がバラバラでどの音にノればいいかわかりません。 現象として以前から観測していましたが「ベースの音ズレ問題」として認識したのは最近です。 緊張してたり、運指に意識が持ってかれていたりすると、ズレるようです。

音ズレ問題に関しては、バンドで名前を出しているベーシストより、バックバンドをやっているベーシストの方の方が得意なことが多いようです。1ステージ通してまったくズレなかったりして、恐るべき能力を感じます。

コーラスを入れるベーシストの方は音ズレ解消した演奏をしながら歌を歌っています。どういう脳みそをしているのか謎です。 歌は歌で息を吐くタイミングと音が出るタイミングはズレています。 つまり、二つの異なるズレを同時に解決しています。 脳みそがマルチコアなのでしょうか?

また、アップライトベースウッドベースは音の立ち上がりがさらに遅いようです。弦がさらに長くなるからではないかと思います。エレキベースのつもりでステージを見ていると、指で弾くタイミングよりさらに遅れて音が聞こえて、違和感を感じます。ただでさえ難しい音ズレ問題なのに、パラメーターが変わります。感覚で合わせてるのに、そのパラメーターが変わる。1ステージ中にエレキベースウッドベース持ち換えれるのヤバくないですか?

ピックと指でも変わるようです。ピックの方が遅く感じます。 これは見る側の感覚なのが演奏する側の感覚なのか、自分の中で、区別がついていません。 指は弦を引っ張って「離す」タイミング、ピックは弦を「押して」離すタイミングを見ているのかもしれません。 実際には視覚的に見えているわけでなく、周期運動を予測しています。 「見て」いる側が、予測する「ヒット」のタイミングを間違って設定しているのかもしれません。

園芸はマネージメントに似ている

似ているところ。植物に代わって、マネージャーが生長したり花を咲かせたり実を付けたりできない。花を咲かせたいとかきれいな樹形にしたいとか、本植物とは関係のないマネージャーのエゴの押し付けがゴールである。手を入れすぎても入れなすぎても枯れるかもしれない、マネージャー自身の恐怖と戦いながらのバランス調整が必要。使えるテクニックは知られているが、マネージャー自身の現場に適用できるかは適用してみないとわからない。マネージャーが植物の支援をしてから結果がでるまで、数ヶ月から一年待つ。

似ていないところ。植物は何も言わないのでフィードバックはあってもヒアリングはできない。植物にモチベーションはないので、本人の目的と会社の目的を一致させるみたいな雲を掴むような調整をしなくてよい。植物が枯れても入れ替えはお金で解決できる。

Ruby in Browser観察日記 その2

世の中でruby.wasmがどのように使われているのか観察して、どんな機能があったら便利そうか妄想するネタにします。

今日はtmtmsさんに教えてもらった MySQL Parameters にお邪魔します。 エントリポイントはlib/inti.rbです。

require 'js/require_remote'
require 'uri'

module Kernel
  def require_relative(path)
    JS::RequireRemote.instance.load(path)
  end
end

require_relative '/lib/jsrb'

query = URI.parse(JS.global[:location].to_s).query.to_s
q = URI.decode_www_form(query).to_h
unless q['tableonly']
  Document.getElementById('header')[:style][:display] = JS::Null
  Document.getElementById('ad')[:style][:display] = JS::Null
end

require_relative '/lib/mysql_params'
start

気になる点は、

require 'uri'

module Kernel
  def require_relative(path)
    JS::RequireRemote.instance.load(path)
  end
end

require 'uri' したあとに、Kernel#require_relativeにパッチを当てています。 これは uri gem中で使っているrequire_relativeにパッチの影響を与えないようにするためです。 実は次のような面倒臭いパッチを当てると、gem内のrequire_relativeとfetchを使うrequire_relativeは共存できます。 簡単に見えるように、普段は一行パッチで説明しています。

module Kernel
  alias original_require_relative require_relative

  def require_relative(path)
    caller_path = caller_locations(1, 1).first.absolute_path || ''
    dir = File.dirname(caller_path)
    file = File.absolute_path(path, dir)
    original_require_relative(file)
  rescue LoadError
    JS::RequireRemote.instance.load(path)
  end
end

次に、気になる点は require_relative '/lib/jsrb' です。 require_relativeなのに絶対パスを指定しています。 これは<script type="text/ruby" src="lib/init.rb" data-eval="async"></script>で読んだRubyスクリプトのURLを正しくとれていないために起きています。 「JS::RequireRemoteを初期化するときに、ベースURLを指定できるようにしたら解消できる?」と、考えています。 次のような使い方になるのかな?

rr = JS::RequireRemote.new(base_url: '/lib')

module Kernel
  def require_relative(path) = rr.load(path)
end

見所はまだたくさんあります。 今日はここまでにしておきます。

Ruby in Browser観察日記 その1

世の中でruby.wasmがどのように使われているのか観察して、どんな機能があったら便利そうか妄想するネタにします。

yancyaさんに教えてもらった 2024 - やんちゃハウスでは次のように使われていました。

 require "js"
 Window = JS.global[:window]
 Document = JS.global[:document]

 class FakeViewers
   def initialize
     @fake_viewers = Document.querySelector('#fake-viewers')
     update_fake
   end

   def set_next
     Window.setTimeout(-> {
       update_fake
       set_next
     }, rand(10000))
   end

   private def update_fake
     @fake_viewers[:innerText] = "※ 現在#{rand(1..20)}名のお客様が、この宿を閲覧されています"
   end
 end

 FakeViewers.new.set_next

同時閲覧者数をランダムで表示する、ジョークプログラムです。

"※ 現在#{rand(1..20)}名のお客様が、この宿を閲覧されています"

こういうのをシュッと書けるのが良いですね。

書き味として気になるのは次の部分です。

Window.setTimeout(-> {
  update_fake
  set_next
}, rand(10000))

第一引数にコールバック関数を渡すのが気になります。 できれば、次のように書きたいです。

Window.setTimeout(rand(10000)) do
  update_fake
  set_next
end

でもこれは、元のAPIと矛盾します。 それはそれで混乱を生むので難しいんですよね。

もしかすると次のように書けたら嬉しいのかもしれません。

loop do
  update_fake
  sleep rand(10)
end

ちなみにKernel#sleepは次のモンキーパッチで動かせます。

module Kernel
  def sleep(time) = JS.global[:Promise].new { JS.global.setTimeout(_1, time * 1000) }.await
end

Ruby in Browserへのモチベーション

2000年代後半にRubyistの間で「ブラウザでRubyが書ければいいのに」という冗談がありました。当時はDartもありませんでした。ブラウザに任意のプログラミング言語が組み込まれるアイデアが一般的ではありませでした。Google Chromeの中の人であれば、技術的には実現できたかもしれません。僕は中の人ではありません。また、当時のRubyはまだ今ほどの人気はありませんでした。他のプログラミングではなくRubyを実装するための投資がされることはなかったと思います。実装しても当時のブラウザの性能では、実用的な速度では動かなかったかもしれません。なんにせよ当時の僕には「実現がほど遠い夢物語でした」。

そして20年近い時が経って、RubyKaigi 2022のkeynoteです。Ruby meets WebAssembly - RubyKaigi 2022です。急に「ブラウザでRubyが書ければいいのに」が手が届くところに来ました。手が届くなら、手を伸ばすしかありません。

#RubyKaigi2024 の思い出 その4

RubyKaigi 2024事後勉強会 - connpass に参加して思い出を噛みしめていました。

inaoさんの仕切りがすごかったです。 LTの発表者だけでなくて、コミッターやドラ係りまで巻き込んでしゃべらせて盛り上げてくのがすごかったです。 RubyKaigi初参加の方が楽しんでくれたのも嬉しいし、歴戦の勇士から「俺たちのRubyKaigi 2025はまだはじまったばかりだ」と前のめりになっているのも頼もしいし、いろんな人がエモイ感想を言っていてRubyKaigiの良さを再確認しました。

tagmorisさんの「動機付けはどこからくるかわからない」は、本当にそうだと思います。僕も突然ruby.wasmがやってきた口です。youchanさんの「踊る阿呆に見る阿呆、同じ阿呆なら踊らにゃ損々」も、そうですよね。来年もCFP出せるようにruby.wasmの改善を頑張ります。

TRICKに挑戦している人が増えているようです。 TRICK2025が盛り上がりそうです。 僕はいまだに燃え尽きていて手が動かせていません。

懇親会

tmtmsさん tomoasleepさん rubocop daemonのFhoteさん と、お話ししてruby.wasmのRuby in Browserの良さを布教していました。 tmtmsさんのユースケースで「lib/main.rbではじめるとrequire_relativeがうまく動かない」という話を聞きました。なるほど、たしかにそうなので、いい方法を考えたいと思います。 僕はRBSに興味がなかったんだけど「コメントでRBSが書けるようになったら、エディタやリンタでは型チェックや補完が効いて、ブラウザで動かすときは無視されると良さそう」て、アイデアを聞いて興味が出てきました。

day1の二次会で一緒したa5_stableさんにidをまちがえていることを教えてもらいました。訂正しました。

okuramasafumiさんも5/27のPolyphiaのライブに行っていたそうです。他のRubyistも観測されているそうで、3000人中に3人Rubyistがいたようです。Polyphia x Rubyistの積集合が3人もいることが驚きです。

二次会

make_now_justさん yancyaさん Dominion525さん と、同席しました。 yancyaさんに、 やんちゃハウスのページにruby.wasmを使っているのを自慢してもらいました。そうそう、こういう使い方をして欲しいんですよ。お手軽なスクリプトを書くのに「簡単なことは簡単に書ける」Rubyがいいんですよ。 make_now_justさんの 主要でもないプログラミング言語200種を一行で解説 #Python - Qiitaの 著者によるコメンタリーつき解説を聞きました。贅沢二次会でした。

ruby_wasm gemをインストールしたい

bundle init
bundle add ruby_wasm

エラーが出ました。

  thread 'main' panicked at /home/ledsun/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rb-sys-0.9.97/build/main.rs:64:59:
called `Result::unwrap()` on an `Err` value: "Stable API is needed but could not find a candidate. Try enabling the `stable-api-compiled-fallback`
feature in rb-sys."
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...

cargo failed, exit code 101

Gem files will remain installed in /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/ruby_wasm-2.6.1 for inspection.
Results logged to /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/extensions/x86_64-linux/3.4.0+0-static/ruby_wasm-2.6.1/gem_make.out

  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/rubygems/ext/builder.rb:125:in 'Gem::Ext::Builder.run'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/rubygems/ext/cargo_builder.rb:34:in 'Method#call'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/rubygems/ext/cargo_builder.rb:34:in 'Gem::Ext::CargoBuilder#build'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/rubygems/ext/builder.rb:193:in 'Gem::Ext::Builder#build_extension'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/rubygems/ext/builder.rb:227:in 'block in Gem::Ext::Builder#build_extensions'
  <internal:array>:54:in 'Array#each'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/rubygems/ext/builder.rb:224:in 'Gem::Ext::Builder#build_extensions'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/rubygems/installer.rb:852:in 'Gem::Installer#build_extensions'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/rubygems_gem_installer.rb:76:in 'Bundler::RubyGemsGemInstaller#build_extensions'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/rubygems_gem_installer.rb:28:in 'Bundler::RubyGemsGemInstaller#install'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/source/rubygems.rb:205:in 'Bundler::Source::Rubygems#install'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/installer/gem_installer.rb:54:in 'Bundler::GemInstaller#install'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/installer/gem_installer.rb:16:in 'Bundler::GemInstaller#install_from_spec'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/installer/parallel_installer.rb:132:in 'Bundler::ParallelInstaller#do_install'
/home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/installer/parallel_installer.rb:123:in 'block in
Bundler::ParallelInstaller#worker_pool'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/worker.rb:62:in 'Bundler::Worker#apply_func'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/worker.rb:57:in 'block in Bundler::Worker#process_queue'
  <internal:kernel>:191:in 'Kernel#loop'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/worker.rb:54:in 'Bundler::Worker#process_queue'
  /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/3.4.0+0/bundler/worker.rb:90:in 'block (2 levels) in Bundler::Worker#create_threads'

An error occurred while installing ruby_wasm (2.6.1), and Bundler cannot continue.

In Gemfile:
  ruby_wasm

念のためrustup updateしました。 変わりませんでした。

Rubyのバージョンの問題かもしれません。

rbenv local 3.4.0-preview1
gem install ruby_wasm
cargo failed, exit code 101

Gem files will remain installed in /home/ledsun/.rbenv/versions/3.4.0-preview1/lib/ruby/gems/3.4.0+0/gems/ruby_wasm-2.6.1 for inspection.
Results logged to /home/ledsun/.rbenv/versions/3.4.0-preview1/lib/ruby/gems/3.4.0+0/extensions/x86_64-linux/3.4.0+0/ruby_wasm-2.6.1/gem_make.out

変わっていなさそうです。 Ruby 3.3.1だと?

ledsun@MSI:~/ruby.wasm-test[1]►rbenv local 3.3.1
ledsun@MSI:~/ruby.wasm-test►gem install ruby_wasm
Fetching ruby_wasm-2.6.1-x86_64-linux.gem
Successfully installed ruby_wasm-2.6.1-x86_64-linux
Parsing documentation for ruby_wasm-2.6.1-x86_64-linux
Installing ri documentation for ruby_wasm-2.6.1-x86_64-linux
Done installing documentation for ruby_wasm after 0 seconds
1 gem installed

A new release of RubyGems is available: 3.5.93.5.10!
Run `gem update --system 3.5.10` to update your installation.

しゅっと行きました。 ruby.wasm開発用にビルドしていたgemが使えたみたいです。 せっかくなのでrbwasmコマンドをつかってみましょう。

ledsun@MSI:~/ruby.wasm-test[1]►rbwasm build -o ruby.wasm
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 75.8M  100 75.8M    0     0  20.2M      0  0:00:03  0:00:03 --:--:-- 30.3M
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 40.4M  100 40.4M    0     0  18.4M      0  0:00:02  0:00:02 --:--:-- 28.8M
==> RubyWasm::BuildSource(3.3) -- Building
  ==> curl -L -o /home/ledsun/ruby.wasm-test/build/checkouts/3.3.tar.gz https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.1.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 21.0M  100 21.0M    0     0  19.0M      0  0:00:01  0:00:01 --:--:-- 19.0M
  ==> tar xf /home/ledsun/ruby.wasm-test/build/checkouts/3.3.tar.gz -C /home/ledsun/ruby.wasm-test/build/checkouts/3.3 --strip-components\=1
==> RubyWasm::BuildSource(3.3) -- done in 1.78s
==> RubyWasm::BaseRubyProduct(baseruby-3.3) -- Building
  ==> /home/ledsun/ruby.wasm-test/build/checkouts/3.3/configure --prefix\=/home/ledsun/ruby.wasm-test/build/x86_64-pc-linux/baseruby-3.3/opt --disable-install-doc
  ==> make -j8 install

おお、これはruby.wasm自体をビルドするときによく見る奴です。 RubyVMコンパイルしています。 考えてみれば当たり前ですが、ruby.wasmをビルドするときと同じ手順を踏むんですね。

せっかくなのでruby.wasmファイルにgemを書き込んでみます。 とりあえず slim をインストールします。 C拡張を使っていなければ何でも良いです。

ledsun@MSI:~/ruby.wasm-test►bundle add slim
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Fetching temple 0.10.3
Installing temple 0.10.3
Fetching slim 5.2.1
Installing slim 5.2.1

bundle exec rbwasm build -o ruby.wasm してみます。

ledsun@MSI:~/ruby.wasm-test►bundle exec rbwasm build -o ruby.wasm
INFO: Using Gemfile: [#<Pathname:/home/ledsun/ruby.wasm-test/Gemfile>]
  ==> tar -C /tmp/d20240528-55886-raholf/usr -xzf /home/ledsun/ruby.wasm-test/rubies/ruby-3.3-wasm32-unknown-wasip1-full.tar.gz --strip-components\=2
INFO: Packaging gem: temple-0.10.3
INFO: Packaging gem: tilt-2.3.0
INFO: Packaging gem: slim-5.2.1
INFO: Packaging setup.rb: bundle/setup.rb
INFO: Size: 51.83 MB

おお、すごい。 ruby.wasmファイルにgemが書き込まれています! はー、つまりGemfile.lockをみて必要なgemを探してきて書き込んでいるのでしょうか?

ついでにC拡張を使ったgemも試してみましょう。

bundle add rexml
bundle exec rbwasm build -o ruby.wasm

こちらは失敗します。

bundler: failed to load command: rbwasm (/home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/bin/rbwasm)
/home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/executor.rb:77:in `system': Command failed with status (1): '/home/ledsun/ruby.wasm-test/build/x86_64-pc-linux/baseruby-3.3/opt/bin/ruby' '-C' '/home/ledsun/ruby.wasm-test/build/wasm32-unknown-wasip1/ruby-3.3-wasm32-unknown-wasip1-full-f7ec96ef2eaad9f30ae602f0a709d006-ext/strscan-3.1.0/ext/strscan' '--disable=gems' '-e' '$top_srcdir="/home/ledsun/ruby.wasm-test/build/checkouts/3.3"' '-e' '$extout="/home/ledsun/ruby.wasm-test/build/wasm32-unknown-wasip1/ruby-3.3-wasm32-unknown-wasip1-full-f7ec96ef2eaad9f30ae602f0a709d006/.ext"' '-e' '$static = true; trace_var(:$static) {|v| $static = true }' '-e' '$0="/home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/strscan-3.1.0/ext/strscan/extconf.rb"' '-e' 'require_relative "/home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/strscan-3.1.0/ext/strscan/extconf.rb"' '-e' 'require "json"; File.write("/home/ledsun/ruby.wasm-test/build/wasm32-unknown-wasip1/ruby-3.3-wasm32-unknown-wasip1-full-f7ec96ef2eaad9f30ae602f0a709d006-ext/strscan-3.1.0/ext/strscan/rbwasm.metadata.json", JSON.dump({target: $target}))' '-I/home/ledsun/ruby.wasm-test/build/wasm32-unknown-wasip1/ruby-3.3-wasm32-unknown-wasip1-full-f7ec96ef2eaad9f30ae602f0a709d006' '--' (RuntimeError)
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/product/crossruby.rb:117:in
`do_legacy_extconf'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/product/crossruby.rb:70:in `do_extconf'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/product/crossruby.rb:48:in `build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/product/crossruby.rb:191:in
`block in build_exts'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/product/crossruby.rb:189:in
`each'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/product/crossruby.rb:189:in
`build_exts'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/build/product/crossruby.rb:210:in
`build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/packager/core.rb:274:in `block in
build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler.rb:404:in `block in with_unbundled_env'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler.rb:659:in `with_env'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler.rb:404:in `with_unbundled_env'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/packager/core.rb:283:in `build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/packager/core.rb:10:in `build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/packager.rb:28:in `package'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/cli.rb:344:in `do_build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/cli.rb:171:in `block in do_build_with_force_ruby_platform'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/tmpdir.rb:99:in `mktmpdir'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/cli.rb:170:in `do_build_with_force_ruby_platform'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/cli.rb:137:in `block in build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/settings.rb:158:in `temporary'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/cli.rb:136:in `build'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/lib/ruby_wasm/cli.rb:34:in `run'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby_wasm-2.6.1-x86_64-linux/exe/rbwasm:7:in `<top (required)>'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/bin/rbwasm:25:in `load'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/bin/rbwasm:25:in `<top (required)>'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/cli/exec.rb:58:in `load'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/cli/exec.rb:58:in `kernel_load'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/cli/exec.rb:23:in `run'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/cli.rb:451:in `exec'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/vendor/thor/lib/thor/command.rb:28:in `run'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/vendor/thor/lib/thor.rb:527:in `dispatch'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/cli.rb:34:in `dispatch'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/vendor/thor/lib/thor/base.rb:584:in `start'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/cli.rb:28:in `start'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/bundler-2.5.9/exe/bundle:28:in `block in <top (required)>'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundler/friendly_errors.rb:117:in `with_friendly_errors'
        from /home/ledsun/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/bundler-2.5.9/exe/bundle:20:in `<top (required)>'
        from /home/ledsun/.rbenv/versions/3.3.1/bin/bundle:25:in `load'
        from /home/ledsun/.rbenv/versions/3.3.1/bin/bundle:25:in `<main>'

なるほど興味深いです。

僕の脳内でイメージするRactorの絵

脳内でイメージするRactorの絵です。

Ractorを最初に知ったときは右のイメージを持ちました。 Threadの中に複数のRactorがあるイメージです。 RubyKaigi 2023でMaNyの発表を聞いて、間違いに気がつきました。 左に更新しました。

今日、Ractorの話をしながら 「Threadの周りに同時実行性の境界をイメージするとしっくりくるなー」と思いました。 それを絵に描きました。

WezTermでデフォルトシェルをWSLにする

結論

.wezterm.lua に次の行を書きます。

-- Start with WSL
config.default_prog = { 'wsl' }

間違った方法

COMSPEC 環境変数C:\Windows\system32\wsl.exe を設定します。 これをやるとバッチファイルが動かなくなります。 たとえばWindowsにインストールした、irbコマンドの実体はバッチファイルです。

C:\Users\led_l>where irb
C:\Ruby33-x64\bin\irb
C:\Ruby33-x64\bin\irb.bat

COMSPEC 環境変数C:\Windows\system32\wsl.exe を設定してirbを起動すると次のように失敗します。

バッチファイルの起動に失敗したところのスクリーンショット

なぜか fish-shell で起動しようとして、コマンドが見つからずに失敗します。 このエラーメッセージからCOMSPEC 環境変数を特定できずにずっと不思議に思っていました。

なぜこのような設定をしたのか?

WezTerm - @ledsun blog で設定していました。

どうもドキュメントの最初の方を読んで設定しました。 で、続きを読むと

It is not recommended to change COMSPEC, keep reading this page of the documentation to learn how to make wezterm run a different program.

COMSPECを変更することは推奨されない。weztermに別のプログラムを実行させる方法については、ドキュメントのこのページを読み続けること。

あー、なるほどねって思いました。

#RubyKaigi2024 の思い出 その3

Day 3

Ruby Committers and the World

Rubyコミッターの皆さんの写真

shopifyさんは35人エンジニアがいて 8人がインフラ(?)チームで 4人がコミッターだそうです。 エンジニア数の少なさにびっくりします。 自社が40人くらいなことと比較して考えると、レバレッジがすごいです。 やはり、ビジネスが大きくなるかの鍵はエンジニアの人数ではなさそうです。

frozen literal

frozen literalが4.0でデフォルトに、 3.4.0からは警告が出るようになるそうです。

仕事でRuby on Ralisアプリケーションを書いている人は、 rubocopでfrozen literalのマジックコメントに警告だしている現場も多そうです。 すごい大きい影響はでないのかな?

mametter さんは色々思いがあるようでした。

ビルドシステム

ビルドシステムが大変という話が出ました。

parse.yに色々手を入れる人が出てきて整備され始めました。 次のRubyの魔境はビルドシステムなのかもしれません。 kateinoigakukunさんビルドまわり強いですよね。 ruby.wasmのビルドもめっちゃ作り込まれています。

GVL

GVLの話が出ました。 PythonがGVL(GIL)を取ろうとしているので意識はしますよね。

GVLをとるとRubyが内部的にロックをとる場所が増えて、シングルスレッドでは遅くなるそうです。 また、スレッド間で衝突する場所が多いとそれほど速くならないそうです。 _ko1さんは、Ractorのために衝突を見つけては減らす、地道な作業をしているそうです。

並行処理はGVLがあればThreadで十分です。 1万スレッドとか立てたければFiberを使う手もあります。 GVLを外して恩恵をうけるのは並列処理です。 しかし、Ractorの事例があんまり出てこないところをみるに、世の中にそんなに並列処理したい需要はあるのでしょうか? そんなにというのは「シングルスレッドの性能を落としてまで、並列処理したいですか?」です。

そんなに並列処理への情熱があるなら、Ractor使えば良いんじゃないのかあ? GVL外したからってRactorより性能がでるわけじゃないんですよ。 既存処理をRactor readyに書き直すのが大変なのはわかるけど、 GVL外したら全部がそうなるんだけなんですよね。

async/await

async/awaitの話が出ました。

「async/awaitを使うとソースコードの見た目がわるくなる」のは、そうなんです。 「ほとんどの関数を非同期で呼んで、時々同期で呼ぶ」ときはバッチリです。 ただ、多くの場合は全部の関数を同期で呼びたいです。 すべての関数呼び出しの前に機械的にawaitが並びます。 ノイジーです。

「auto fiber を使えば、非同期にできるものが自動的に非同期できるので便利」もそうですよね。 実際に、使ったことはないので、感触まではわかっていません。

「関数を書くときにasyncか最初からわかるの?」という問いもわかります。 ブラウザではfetch APIとか外部リソースを呼ぶときは、APIがasync関数になったので、わかりやすいです。 サーバーサイドだと、 asyncを使うか予測するのは、難しい気もします。 しかも、Rubyのサーバーサイドプログラミングを書くときって、ほとんどの人は同期の世界でプログラミングしてますよね? Node.jsのときはイベントループのイベントハンドラーを実装する気分で書いているから、わかるんですよ。

同期的なプログラミングを書くC#でもasync/awaitが結構上手く行っているので、C#を参考にした方がいいのかもしれません。 C#ではたくさんのasync/awat書いたことがありません。 JavaScriptでもテストコードでは書きますが、プロダクトコードではたくさんは書いていません。 外部リソースをつかうときはわかりやすいのですが、ブラウザ内で動くロジックで非同期関数をつかうと、どうしても同期のタイミングを制御する仕組みを作り込む必要があります。 そこが面倒なのでなるべく速く動く同期関数を書いてしのいでいます。 C#のasync/awaitには「時間の掛かる処理をTaskを返す関数で書いて、awaitつけて呼ぶと勝手にスレッドつかって実行して、終わったらメインスレッドに同期する」仕組みが.NET Frameworkに組み込まれているので、そこまで作り込むと使い勝手が良いのかもしれません。 まあ、こういう仕組みが入ってくるとRubyっぽくないなって感じはします。 Railsのようなフレームワークがやるのはありです。 load_asyncが既にあることを考えると、そのサポートに必要なのはasync/awaitではなさそうです。

この話題では、Promiseが入る前にJavaScriptコミュニティーの強い人が言っていた「コールバックヘルになるのは設計が悪いのであって記法は関係ない」という主張を思い出します。 async/awaitも似たような点がありそうです。 async/awaitで解決したいことって本当は記法の話ではないようにおもいます。 C#には「画面が固まらないWindowsデスクトップアプリケーションを増やしたい」みたいなモチベーションがあったように思います。 いまのところRubyはサーバーサイドプログラミングが主戦場で、GUIやブラウザで動かすシチュエーションが少ないです。 ruby.wasmをつかって、inブラウザプログラミングが増えたら、何かあるんですかねえ? いまのところはkateinoigakukunさんが作ってくれた、Fiberベースのスケジューラーとawaitメソッドで何も困らないんですよねえ。 Promise.allとかはライブラリーで吸収できそうです。

というわけで、僕は「今のところは要らない派ですが、ruby.wasm関連で欲しくなるかもしれないから気になるなあ」というステータスです。

Turning CDN edge into a Rack web server with ruby.wasm

remore さんの、CDNのエッジワーカーでrackを動かす話です。

FastlyではCDNのエッジをpopて呼ぶそうです。 いい名前ですね。すごくそれっぽいです。

いまのところレスポンスに1000msかかって辛いそうです。 アプリケーションなら起動に一秒かかってもいいですが、 CDNだとレスポンスが重要なので厳しいですよね。

あとで質問したところ、なぜrackが遅いのかはわかっていないそうです。 wasm用のプロファイラがなくて、遅い原因を特定するのが難しいそうです。

途中、C言語の関数を実装してたところが追えませんでした。 これも質問したところ、エージワーカーで、つまりWasmの外側で動く関数を実装したそうです。 witの何かのツールつかってバインドしてたそうです。

起動を速くする手法として、起動後のバイナリをWebAssemblyに焼き込んでおく方法があるらしく、それを試してみたいと聞きました。 あとで、何かの記事で見たのを思い出しました。 WebAssemblyで、JITコンパイラに迫る高速なJavaScriptエンジンを実装へ。Bytecode Allianceが技術解説。JavaScript以外の言語でも - Publickeyに載っているWizerってやつかなあ?

あと、エッジワーカーの事例として「リクエスト元に応じて異なるQRコードを返す」を聞けて良かったです。

lunch

なはーとの向かいのブリトー屋さん MILCOMIDAS (ミルコミダス) - 美栄橋/メキシコ料理 | 食べログ でテイクアウトして、緑ヶ丘公園で食べました。

お昼に食べたブリトーの写真

よく知らなかったのですが、ブリトーにはお米が入っています。 メキシコ風タコスおにぎりみたいな感じです。

そして、自分の発表に向けて最後の練習をしました。

Speeding up Instance Variables with Red-Black Trees

tenderlove さんの発表。

発表の構成がうまいですよ。 僕なら「ObjectShapeは削除されないから、キャッシュにつかうなら進研ゼミでやった赤黒木がぴったりだとおもう。やってみたら本当にぴったりだったよ」というなんの盛り上がりもない話をしそうです。 次のエピソードを入れてくるところが良かったです。

赤黒木っで実装難しいじゃん。 でも、Articles/Haskell/Purely Functional Data Structures (Okasaki).pdf at master · aistrate/Articles · GitHubRubyのパターンマッチを使えば! ほらね!

アルゴリズムの勉強にもなるし、パターンマッチの使い所の紹介にもなるし、聞いてて得した感じがしました。 そこから「ObjectShapeのキャッシュに赤黒木を使う」完璧な対応を見せてくるところが、またテクくてよかったです。

ERB, ancient and future

帽子がオシャレなm_sekiさんの写真

ERBはruby.wasmでもつかえて便利なんですよ。 この発表のように「ERBはCGI以外にもつかえるはず」ってアイデアがあったからなんですよね。 また、テンプレートのなかで別のテンプレート呼べるのもbindingを渡せる設計にしてくれたからです。

ていうのを、25年前に25年以上つかえるように設計して、バチッと決めるのなんなんでしょうね? オーパーツ

Using Ruby in the browser is wonderful.

このあとのセッションはお休みにして、自分の発表の準備をしました。

Matz Keynote

パフォーマンスについて何度も言及していたことが印象的でした。 また「namespaceが入ったらRuby 4.0にする」もインパクトのある発言でした。 僕の中では、無根拠にnamespaceがproduction readyになるには3年、あるいは5年くらい掛かる予感がしています。 すると、Ruby 4.0はそれぐらいかなあ?と思いました。

Closing

RubyKaigiはたくさんのスタッフの方達に支えられています。 ありがたいことです。

RubyKaigiを支えるスタッフの皆さんの写真

帰路

ゆいレールで空港へ向かいます。

day 1からday 3まで、3日間、お天気に恵まれて良かったです。

dinner

A&Wで食べた夕飯の写真

沖縄に来たからには、A&Wを食べて帰らなくてはいけません。

#RubyKaigi2024 の思い出 その2

Day 2

Leveraging Falcon and Rails for Real-Time Interactivity

Samuel Williams さんの非同期に関する発表です。 具体的にはAsyncやFalconの話です。 GitHub - socketry/flappy-bird を動かす話です。

動くソースコードが公開されています。

git clone git@github.com:socketry/flappy-bird.git
cd flappy-bird
bundle
bin/rails db:migrate
bin/rails s

flappy-birdを起動した画面

Falcon X Rails でWebSocketを使ったアプリケーションのサンプルとして丁度良さそうです。 ソースコードを見るとこんな感じでとてもシンプルです。

require 'async/websocket/adapters/rails'

class GameController < ApplicationController
  RESOLVER = Live::Resolver.allow(FlappyView)

  def index
    @view = FlappyView.new
  end

  skip_before_action :verify_authenticity_token, only: :live

  def live
    self.response = Async::WebSocket::Adapters::Rails.open(request) do |connection|
      Live::Page.new(RESOLVER).run(connection)
    end
  end
end

Async::WebSocket::Adapters::Railsは、Async::WebSocketというWebSocketのためのgemの一部のようです。 Live::PageLive gemの機能のようです。 細かい機能はよくわかりません。

以前faye-websocketを使って、WebSocket通信をする sinatraアプリケーションを作ったことがあります。 アプリケーション開発者視点でどういう点が違うのか気になっています。

まだActionCableのパッチが本家に入っていないとかで、Ruby on Railsの完全対応はもう少し掛かるようです。

Does Ruby Parser dream of highly expressive grammar?

ydah_ さんの発表です。 正直、パーサー周りはよくわかりません。 細かい感想が言えません。 が、ydah_さんの熱い思いが伝わる良い発表でした。 「よくわからんけど、なんかすごい」のがRubyKaigiっぽくて良かったです。

壇上のydah_さんの写真

このステージの照明がかっこいいです。

lanch

ydah_ さんの発表後の流れで、パーサー陣営とお昼を食べに行きました。スピーカーとコミッターばかりの濃いやつです。 ydah_ さんの発表がRubyKaigiぽくて良かったという話をしました。

LRパーサーに興味のあるスピーカーとコミッターの写真

RubyGems on ruby.wasm

我らが kateinoigakuun さんの発表です。 僕的にベストトークです。

ruby.wasmでmastdonを動かす、だいぶ意味がわからないデモがみれました。 gemが動くし、C拡張も動くんですよ。 ここまでは、直近のコミットから推測できていました。 ブラウザでmastdonが動くってことは、外部からHTTPリクエストを受け付けられるのです。 不思議です。 どうもServiceWorkerをつかってHTTPリクエストをフックしているぽかったです。

C拡張を動かすには、C拡張のビルドの仕組みにも変更が必要だったようです。 Feature #20345: Add `--target-rbconfig` option to mkmf - Ruby master - Ruby Issue Tracking System で対応しています。 仕組みとしてはRubyとC拡張を両方WebAssemblyにコンパイルしたうえで、 WebAssemblyバイナリ上でC拡張の関数ポインタの場所を良い感じに調整して RubyからC拡張の関数を参照できるようにしているみたいです。 みたいです・・・っていうか、全然よくわかっていなくて、 雰囲気で書いています。

また、Futureの話が熱いです。 ロードマップの図にサイズの図が熱いです。

ロードマップの図

サイズの図

Rubyインタプリタのサイズが小さくなることを明確なゴールにしているのがすごく良いんですよ。 セッション後に質問したら、めっちゃ小ちゃくなるには年単位の時間が掛かりそうとのことでした。

個人的な予想では、ruby.wasmをつかったRuby in browserが流行るかは、インタプリタのサイズよりキラーアプリの有無が鍵だと思っています。RubyにとってのRuby on Railsみたいなやつです。 商売として取り組むには、新しい技術に挑戦するリスクをとる見返りがほしいですよね。

インタプリタサイズの縮小が実現する頃までには、キラーアプリか、キラーアプリを作れる土壌を用意しないといけないなー、と思っています。 そういうのもあって僕の発表では、フレームワークっていうわかりやすい飛び道具を打ってみました。

参考: RubyKaigi 2024 で学んだこと・社員として参加する理由ruby.wasm関連の感想があって面白いです。

RuboCop: LSP and Prism

最高にかっこいいkoicさんです。

最高にかっこいいkoicさんの写真

自主休憩

三日目には自分の発表があるので、スケジュールをタイトにしたくありませんでした。 ここで体力温存がてら、お土産を買いに行きました。 なはーとの国際通りに近い立地が最高です。

pockeさんが食べてて美味しそうだったので、ブルーシールでチョコミントを食べました。

ブルーシールのチョコミントアイスの写真

参考: 2024-05-15 - diary

Lightning Talks

Visualize the internal state of ruby processes in Real-Time

hogelogさん

自作のmetorics_monitor gemの紹介です。

The Frontend Rubyist

largo さん

https://koans.idogawa.com/https://rubygems.org/gems/jsg の紹介です。

ruby.wasm仲間です。 一緒に盛り上げて行きましょう。

Enjoy Creative Coding with Ruby

chobishibaさん

ps5.js のラッパーのp5rbを使ったクリエイティブコーディングの紹介です。 p5.rb Editor を使っているのでこちらもruby.wasm仲間でした。

参考: p5.rbでのコーディングあそびをはじめました|bash note

Rearchitect Ripper

金子さんすごすぎる。 通常セション、LT、Commiter and the worldの三連投です。

The Journey of rubocop-daemon into RuboCop

fothe さん

rubocop-daemonというrubocop用のサードパーティーのgemを作っていたら、 突然rubocop本家に取り込まれたそうです。 夢のある良い話です。

The test code generator using static analysis and LLM

mikiko_bridge さん

omochiの紹介です。 テストがないコードを探してきて、 LLMでテストコードを生成するgemだそうです。 便利そうです。

Contributing to the Ruby Parser

S-H-GAMELINKS さん 記憶がありません。

Improved REXML XML parsing performance using StringScanner

naitoh さん

Ruby製のXMLパーサーREXML を速くした話です。 regexp を string scannerに置き換えたら5%くらいの高速化したそうです。 YJIの効きが良くなったそうです。 メモリ使用量が減ったからのようです。

Hotspot on Coverage

shia さん

ISCON用にホットスポットを探す akainaa | RubyGems.org | your community gem host を作って役に立てた話です。 本来はテストの薄いところを探すためのカバレッジレポートを、実行回数の多いホットスポットを探すためにつかう話です。 逆転の発想がかっこいいです。

ハイライト範囲をかっこよくするためにprismつかっているそうです。 結果のわかりやすいUIにこだわっているところもかっこいいです。

Drive Your Code: Building an RC Car by Writing Only Ruby

hachi さん

picorubyを使った電子工作で作ったラジコンカーの紹介です。 物理的に動く物が作れるの良いですね。

An anthropological view of the Ruby community

英語がちょっと難しかったです。 というか力尽きて寝てました。

dinner

株式会社ラグザイアはプラチナスポンサーをやっていてブースを出しています。

スポンサーのロゴを表示するスライドの写真

夜はブース運営をしてくれてる会社のメンバーと焼鳥屋さんに飲みに行きました。

焼鳥屋さんの昼間の姿の写真