型 再考

かたつけてい

片月亭

黒曜  / @kokuyouwind

  • 黒曜 / @kokuyouwind

  • Rubyと関数型言語チョットワカル

    • (最近は関数型言語全然触ってない)

  • カンファレンス実況勢

    • 初参加の RubyKaigi 2015 から11年

    • ​​実況芸で食わせていただいております(?)

$ whoami

名古屋Ruby会議04 (2019)

入門 関数型-ish プログラミング on Ruby」で登壇

名古屋といえば関数型プログラミングと型でしょ!!!

それから7年…

  • TypeScriptが完全にデファクトスタンダード化

    • 2019年はまだ過渡期(Babel, ts-loader, webpack, …)

    • 今や型レベルプログラミングや型安全性の話が一般的に

  • Rubyでも型システムの整備が進んだ

    • RBS(型記述言語)がbundled gem化 (2020/12)

    • Steep, TypeProfなどの周辺ツールが継続開発

    • インラインRBS記法の実験的サポート (2026/03)

世はまさに、大「型システム」時代!
(?)

ここらでちょっと振り返ってみよう

  • そもそも型ってなんだっけ?

  • なんでRubyに型システムがあると嬉しいんだっけ?

  • Ruby言語と別に型専用のRBS言語があるのはなぜ?

🤔

アジェンダ

  • 型・型システムの基礎

  • 型を後付けする難しさ

  • Rubyの型システムの歴史振り返り

  • まとめ

アジェンダ

  • 型・型システムの基礎

  • 型を後付けする難しさ

  • Rubyの型システムの歴史振り返り

  • まとめ

そもそも型ってなんだっけ?

  • 型をつける・型がある・型がほしい

  • 型推論・型安全・型注釈

  • いろんな語があるけど「型」って結局なに???

🤔

みんな大好き 型システム入門 (TAPL)

Pierce 著 / 住井 監訳, オーム社, 2013 (和訳版)

「型」を明確に定義している文は
見当たらない!

「型システム」の定義ならある

「型システムとは、プログラムの各部分を、
それが計算する値の種類に沿って分類することにより、

プログラムがある種の振る舞いを起こさないことを保証する、

計算量的に扱いやすい構文的手法である。」(TAPL p.1)

※「十分に意味のある具体的な定義を与えるのが難しい」と前置きあり

「型システム」の定義から「型」を考える

  • プログラムの各部分を、値の種類に沿って分類する

    • この「分類」を「型」と呼んでいることが多そう

    • → 以降のスライドでは「型」をこの意味で用いる

「型システムとは、プログラムの各部分を、
それが計算する値の種類に沿って分類する
ことにより、

プログラムがある種の振る舞いを起こさないことを保証する、

計算量的に扱いやすい構文的手法である。」(TAPL p.1)

「型」の直感的理解: 値の集合

型 =「プログラムが表現しうるすべての値から、

ある性質を満たす値を集めたもの」

ダックタイピングと型

#each に応答する」性質を満たす集合(型)も考えられる

→ def obj.each したオブジェクトもすべて要素になる

 

クラスは型だが、クラスだけが型ではない

動的型付け(動的検査)

  • プログラム実行時に「ある性質を満たす値か」をチェックする

    • 1.each → NoMethodError

    • 1 が #each に応答する集合の要素か」を動的に検査している

動的型付け(動的検査)は型システムか?

 (動的型付けでは) ヒープ中の異なる種類の構造を区別するのに実行時の型タグが利用される。「動的型付けされる」といった言い回しは誤っているといって差し支えなく、おそらく「動的検査される」と言い換えるべきであるが、標準的に使われる用語法である。(TAPL p.2)

→ これは(構文的手法としての)型システムではない

「型システム」の定義をもう一度見てみる

「型システムとは、プログラムの各部分を、
それが計算する値の種類に沿って分類する
ことにより、

プログラムがある種の振る舞いを起こさないことを保証する、

計算量的に扱いやすい構文的手法である。」(TAPL p.1)

「値そのもの」ではなく「プログラムの各部分」を型で分類する

→ 実行前のプログラムに対する手法が「型システム」

「型」の形式的定義: 項の型付け用ラベル

型付け規則によって項につけるラベル = 型

型付け規則と型安全性

  • 型付け規則は何でも自由に決めればいいというものではない

    • 「すべての項に untyped をつける」型付け規則はなんの役にも立たない

  • 型安全性(型健全性) を満たすよう型システムを設計する

    • 型付け規則によってすべての項に型をつけられるなら、
      実行時に各項がその型の値になり、実行時に型エラーにならない

    • 例: 「x と y が整数ならば x + y も整数」はあらゆる x と y で成立する

  • 何を実行時エラーとするかは言語次第

    • 型システムの設計によって、どういう種類の「不正な振る舞い」を
      防げるかが変わる

不完全性と「不正な振る舞い」

  • TAPL「型システムは、項が実行時にどう振る舞うかの静的な近似」(p.2)

    • 「実行時エラーにならないコードが型付け規則を満たす」とは限らない 

    • 上記を満たすなら「型完全」という

型付け規則と型注釈・型推論・型検査

  • 項に対してどう型をつけるか

    • 型注釈 = 人が書く

    • 型推論 = ツールが導出する

    • 基本は上記の組み合わせ

  • 静的型検査

    • 型付け規則で項に正しく型がつけられないなら、実行時エラーを起こす可能性がある

型・型システムの基礎 まとめ

  • 型システム: 項を値の種類で分類する構文的手法

    • プログラム全体に型がつけられたときに、
      実行
      時の型エラーが発生しないことを保証する

  • 型: その分類のラベル

    • 直感的には値の集合

    • 形式的には型付け規則に用いるラベル

  • 「なにを実行時型エラーとするか」は言語しだい

    • ​それに合わせて型システムを設計する必要がある

アジェンダ

  • 型・型システムの基礎

  • 型を後付けする難しさ

  • Rubyの型システムの歴史振り返り

  • まとめ

型を後付けする難しさ

「型検査を考慮して設計されていない言語に型システムを組み込むのは困難な問題である」(TAPL p.7)

  • 理由1: 型検査を困難にする機能・イディオムが推奨されがち

    • 例) ダックタイピング、define_method、ブロックの制御フロー

  • 理由2: 型注釈を考慮した具象構文は複雑になりがち

    • 後付けだと「注釈をどこに、どう書くか」が問題になる

なぜRubyの静的型付けは難しい?

  • 推論以前に「型」をどう設計するかがそもそも難しい

    • tap メソッドは break するブロックを渡されたときだけ自身を返さない

    • x, *y, z = a のような代入は配列内位置で異なる型が必要

  • 「Rubyらしいプログラム」を拒否せず型を付けるには高い表現力が必要

    • 推論だけで導出するのは難しい (型注釈が必要)

表現力の代償

  • 表現力を上げると、型システムの「計算量的に扱いやすい」から遠ざかる

    • 推論が難しくなる

    • 注釈が長く複雑になる

    • 言語と型システムを合わせて設計すれば一定回避できることもある

  • Rubyのイディオムは型推論と相性が悪く、型注釈が必要

    • 誰が書くのか?

    • どうやって型を表現するのか?

漸進的型付け

  • Gradual Typing [Siek & Taha 2006]

    • 分からない部分は未定にし、分かっている部分同士の矛盾だけ拒否

  • 近年、既存の言語に型を後付けする標準的な方法になっている

    • TypeScript any

    • Python Any

    • RBS untyped

漸進的型付けで解決しない問題

  • 全部 untyped = 何も検査していないのと同じ

  • untyped を受け取った式の結果も untyped (規則で型が決まらない)

  • 効くのは「untyped と接しない、まとまった領域」を作れたとき

    • その領域では結局「高い表現力の型システム」が必要

型を後付けする難しさ まとめ

  • 型システムを後付けするのは難しい

    • 型推論しづらい機能・イディオム

    • 複雑な型注釈の構文設計

  • Rubyは普通のプログラムでも表現力の高い型が必要

    • 複雑な型注釈を誰がどう書くか

  • 漸進的型付けが後付の標準手法になっている

    • 型検査の恩恵を受けたい領域では結局ちゃんと型を付ける必要がある

アジェンダ

  • 型・型システムの基礎

  • 型を後付けする難しさ

  • Rubyの型システムの歴史振り返り

  • まとめ

Rubyの型システムの歴史振り返り

2014: Ruby 3 の目標に「静的型検査」

  • Matz RubyKaigi 2014: Ruby 3へ向けて実装したいアイデア

    • Concurrency, JIT, 静的型付け

  • 静的型付けのメリット

    • 型情報を利用したパフォーマンス改善

    • 実行前の静的型検査によるエラーチェック

    • ドキュメンテーションとしての型

2016: 「型注釈は書きたくない」

  • 「無駄だと思うものは誰も書きたくない」

    • 型指定は「むしろ積極的に外すべき」

  • Matz 2016 "We are not going to add any kind of type annotation to Ruby"

    • 「Ruby本体に型注釈の記法は足さない」というスタンス

  • 目指すのは Structural Subtyping + 型推論で、注釈なしの型検査

2017: 型検査器が複数生まれる

  • Steep (Soutaro, 2017)

    • sig/*.rbi の別ファイルシグネチャ + コメント注釈

  • Sorbet (Stripe, 2017)

    •  sig { } DSL + 実行時検査

  • 「人間が注釈を書く」前提

    • 注釈なしの型検査」を目指すMatzの思想と別ルートではある

    • Ruby自体には型注釈記法を導入しない

2019: シグネチャ言語だけ共通化へ

  • 2019-03 ruby-signature が ruby org 配下に誕生

    •  Steep のシグネチャ言語を切り出したもの

    • 後に RBS にリネーム (2020)

  • 検査器は統一せず、シグネチャ言語だけ共通化 = RBSの原点

    • Steep は移行、Sorbet は互換を表明 (実対応は2025)

RBSはなぜ「別言語」になった?

  • Rubyの構文に型注釈記法は入れない(Matzの意向)

    • 人間が書かなくていいものを書きたくない

    • 構文を変えると特定バージョンへの移行を強いる

  • コメントでなく別ファイルにする Steep の設計を継承 (2017年から sig/*.rbi)

    • C実装の組み込みライブラリや他人のgemにも、外から型を付けられる

    • コードを変えずに始められる (.d.ts / .h と同じ発想)

  • ツールに依存しない中立な言語

    • 型情報をツール間で共有するため

2020: Ruby 3.0 release

  • 2020-12 Ruby 3.0.0

    • rbs / typeprof が bundled gems になった

    • Steep・Sorbet は非同梱

    • Matz が、「型注釈を書くことを Ruby本体として推進しない」と判断

  • 2021-05 「別ファイルは不便、インラインで書きたい」という要望が出る

    •  Matz「型宣言のための新構文は追加しない。
                    yard コメント内の RBS 構文なら yard 側の判断」

2024-2025: 型注釈のインラインコメント化

  • 2024: rbs-inline (soutaro)

    • RBS を Ruby コードのコメントとして書けるように

    • Ruby の構文は変えないまま、別ファイルの書きにくさを回避

  • 2025: Sorbet が RBS コメント記法をサポート (Shopify)

    • 既存のDSL記法と併用可(opt-in、experimental)

    • RBS で Sorbet の全機能を表せるわけではない

2026: RBS 4.0 でインライン記法に対応

  • 2026: RBS 4.0, Steep 2.0

    • インライン RBS 記法を rbs gem 本体に統合 (experimental)
    • Steep 2.0 でインライン RBS 記法を直接読めるように

Rubyの型システムの歴史振り返り まとめ

  • Ruby 3 の目標として「静的型検査」が立てられた

    • Matz「型情報は欲しいが、型注釈をRuby構文には入れない」

  • 複数の型検査器が独立して開発

    • ツール間での連携のため、中立のシグネチャ言語が必要に

    • Steepのシグネチャ言語を ruby-signature として切り出し、後のRBSに

  • 現在はRBSのインライン記法が実験的にサポートされている

アジェンダ

  • 型・型システムの基礎

  • 型を後付けする難しさ

  • Rubyの型システムの歴史振り返り

  • まとめ

まとめ

  • 型・型システムの基礎

    • 型システム = 項を分類して不正な振る舞いを防ぐ構文的手法

    • 型はその分類のラベル

  • Rubyに型を後付けする難しさ

    • 普通のRubyコードを拒否しないには高い型の表現力が要る

    • Rubyらしいイディオムは型推論と相性が悪い

  • Rubyの型システムの歴史

    • Matz「型情報は欲しい、でも型注釈は構文に入れない」

    • シグネチャ言語 (RBS) を別ファイルやコメントにする判断

再考

かたつけてい

片月亭

黒曜  / @kokuyouwind

最高