entry-header-eye-catch.html
entry-title-container.html

entry-header-author-info.html
Article by

PHPerが見るRubyKaigiタイムテーブル Day 2

こんにちは! Platform Divisionのうさみ(@tadsan)です!

Day 1の紹介記事に引き続き、全三日間のうちのDay 2について、普段PHPを書いている視点からの紹介を綴らせていただければと思います!

The AST Galaxy to the Virtual Machine Blues

rubykaigi.org

個人的に師匠と仰いでいる、ささだこういちさんの発表です。

ささださんはRubyist Magazine(るびま)の発起人のひとりで、かつては「YARV: Yet Another RubyVM」と呼ばれていた仮想機械型インタプリタの開発者で、現在もRubyコミッターとして活躍しています。

現代において、多くのインタプリタは仮想機械(Virtual Machine)と呼ばれる構造をとっています。この種類のインタプリタでは実行時に構文木をそのまま解釈・実行するのではなく、シンプルな命令列にコンパイルした上で実行します。

ここでいう「仮想化」というのはOSの仮想化と根本的には似たようなものなのですが、インタプリタの実装方式としてのVMについては、ささださん自身がRubyist Magazineで説明されています。

magazine.rubyist.net

これは実用されているスクリプト言語のほとんどに当てはまり、PHP, Python, Perl、さらにはEmacs Lispなど多くの言語の標準的なインタプリタがこの例に当てはまります。さらには現代ではJITコンパイルすることで、もっと先の最適化を進められる見込みもあります。

PHPやEmacs LispにもJITやnative compileの導入・改善が進められ、RubyにおいてもMJIT, YJIT, RJIT, ZJITといった複数のアプローチが提案されてきたことは、みなさまもご存じの通りです。

これこそ動的言語の最適化の王道であり、私もこれが進むべき道なのだと考えてきました。 ……最近までは。

少なくともRuby 1.9の頃にYARVがマージされた時代を体験していたわれわれとしては、ささださんといえば日本でインタプリタをVM化した立役者であり第一人者という先入観を持ってしまっているのですが、これはJITなしのRubyアプリケーションの提案です。

昨日の記事で「インタプリタは構文解析されたコードを毎回実行する」という話をしました。

リアルなマシンのサーバーでRubyやPHPが直接実行されているような場合は、実行中にコードが書き換わったものを即時反映したり、新しいコードが再デプロイされたらアプリケーションサーバーを再起動するといったことを考える必要がありました。

一方で、アプリケーションがコンテナ化されている現代においては、コンテナが起動してからシャットダウンするまでのライフサイクルの中で、アプリケーションコードや依存ライブラリのコードが変化するということは原則としてありません。それなのにインタプリタがアプリケーションの実行プロセスの中でファイルを構文解析して動的にロードする必要はあるのでしょうか。

ささださんの提案するASTroはアプリケーションコードの構文木を最適化した上で、アプリケーションと特殊化されたインタプリタを一体のネイティブコードとしてコンパイルする技術であるようです。

セッションの説明ではまだ研究段階とのことではありますが、将来的な動的言語のありかたとして非常に有望な技術のように感じます。

Making the RBS Parser Faster

rubykaigi.org

SteepというRubyの静的型チェッカーを実装されている松本宗太郎さんによる発表です。

PHPにおいてはPHPStan、Psalm、最近ではMagoといったいくつかの型チェッカーがしのぎを削っていますが、RubyにおいてはSteepやSorbet、それからRubyに組み込みのTypeProfなどのツールがあります。それぞれの関係などについては以下の記事で紹介されています。

note.com

PHPの立場からの紹介をしておきますと、PHPは言語組み込みの「型宣言」と、静的解析ツールなどが実行を伴わない分析に利用するPHPDoc上の「型注釈」という二元的な型についての記述を組み合わせる構造になっています。

<?php

/**
 * 型がPHPDoc(=コメント)にだけ書かれ、実行時の保証がない関数
 * 
 * @param int|float $n
 * @param int|float $m
 * @return int|float
 */
function add_legacy($n, $m)
{
    return $n + $m;
}

/**
 * 実行時に型が、すくなくとも int または float であることは保証される関数
 */
function add_modern(int|float $n, int|float $m): int|float {
    return $n + $m;
}

PHPでは基本的に演算子オーバーロードがないので、 + で受け入れられるのは数値型か、配列同士の結合の2通りだけです。 (ただしDay 1で紹介した BcMath\Number やそのほかの拡張などの例外は存在するのですが、話がややこしくなるので一旦忘れてください)

add_legacy(['a' => 1], ['b' => 2]) のような関数呼び出しはPHPDocの型と反しており、静的型チェッカーで解析するとエラーを検出するはずですが、実行してみると通ってしまうはずです。

一方で add_modern() 関数を同じように呼び出すと、PHPは関数呼び出し時に毎回型チェックを行って、型宣言に反する値で呼び出された場合は実行時エラーを出すようになっています。つまり型宣言通りの実装が履行されているかは、静的型チェックですり抜けてもユニットテストで実行するか、それでもすり抜ければエラーログで検出できるようになっています。

処理系が透過的に検査するとはいえ呼び出し時の毎回の型チェックはいかにもコストがかかりそうですし、概念的にはN+1問題的なことが起こりそうですが、個人的な経験ではJIT有効化時には最適化の恩恵の方が大きいように思います。

一方でPHPでは型宣言が検出できるのは基本的な型とユニオンなどいくつかの型演算だけで、 array $book のような型宣言は可能ですが、その配列の中に何が入っているかは宣言できません。それを補完するためにPHPDocで @param list<Book> $book のような型注釈を書くことが文化として定着しています。

翻ってRubyでは、2020年公開のRuby 3.0からRBSとよばれる型宣言ファイルの仕様が公開されました。これはRubyのソースコードそのものではなく、別ファイルに型シグネチャを書けるようにしたものです。

これはC言語の *.c に対する *.h や、OCamlの *.mli 、F#の *.fsi のようなものと認識しています。C言語のヘッダファイルはともかくとして、実用上必須ではないヘッダファイルや、コードと別の場所で管理されたドキュメントなどはあまり積極的に使われないようなイメージがあります。

これに対して、松本宗太郎さんの主導するrbs-inlineはコメント中に型を書くことでRBSの表現力をコードと一体にできるようになったと承知しています。

いかにして動的型付けのRubyに静的な型検査を持ち込むか? SteepとRBSが目指すもの - Findy Engineer Lab

Rubyにも以前からYARDという記法が使われてはいました(私も使っていました)が、RBSの表現力をもって、より発展的な型が書けるようになったという状況です。

私はSorbetやSteepの個別の型付けの戦略について特に詳しくないのですが、PHPのような気分での型付けや型推論を阻む要素として、Rubyは伝統的にダックタイピングなので、本質的にNominal Typingとの相性が破滅的に悪そうだということです。

……ということを長々と書いてしまったものの、今回の発表はRBSパーサーそのものをSorbetのような外部ツールと相互運用できるような改善と、それに伴うパフォーマンス問題の解消に焦点があるようです。

TypeScriptなどは異常に複雑な型が書けることで非常に有名ですが、同じように複雑な入れ子の構文によって高度な型が書けるようになると、いかにも速度低下の原因になりそうなところであります。

個人的には快適な型付けにはコーディングをブロックしないオンザフライでの型検査結果のフィードバックが不可欠だと信じているので、開発体験をさらによくするポテンシャルのある改善に期待しています。

Practical TypeProf: Lessons from Analyzing Optcarrot

rubykaigi.org

こちらはTypeProfを開発しているmameさんこと遠藤侑介さんの発表です。

OptcarrotはRuby 3.x以来CPUバウンドなケースにおけるベンチマークのために組み込まれているNESエミュレータの実装です。

TypeProf自体は実装をベースに型推論を行うものですが、Ruby自体が動的なケースがあまりに多く、型推論によってリアルワールドで実用的な型が構築できるかはかなりの課題があることは容易に想像がつきます。

evalやJSON などのデコード結果など、静的な型が付けられないケースがあちこちにあり、Rubyで自然に多用されるmapのような高階関数がどのように処理されるか非常に関心があるところです。

まとめ

本日RubyKaigi 2026 Day 1が終わったところですが、私の興味とする分野のテーマを選んで聴講しても尽きないのが本当に素晴らしいことです。

明日は今回紹介したように私の興味に近い型関連の話がより聞けそうなので楽しみです!

20191219022007
tadsan
1989年生まれ。北海道砂川市出身。北海道工業大学を留年した後、札幌のSSL証明書リセラーでのアルバイトを経て2012年にピクシブ入社。pixiv運営本部 技術基盤チームリーダー、技術マネジメント室所属。WEB+DB PRESS連載、PHPカンファレンス登壇などでPHP開発の知見の普及に努める。2017年よりEmacs php-modeのリードメンテナーを引き継ぐ。