Rustユーザーから見た
Scalaの型クラス
GitHub:yubrot
LT at #scalabase_scala 2022/03/04
yubrot(ゆーぶろ)
https://yubrot.github.io/profile/
「Rust/Haskellユーザーから見た」としたかったが、GitHub見返したら年単位でHaskell書いてなかった...のとHaskellも含めると完全にLTに収まらない
導入: 型クラス in Scala 3
以下、糖衣構文については省略します
型クラス in Scala 3: 定義
(例: 文字列から変換できる、という特性を表現した型クラス: FromString)
Scalaにおいて、型クラスは単にtraitで定義される
型クラス in Scala 3: インスタンスの実装
(例: 整数は文字列から変換できる: IntFromString)
型クラス in Scala 3: インスタンスの実装
(例: 整数は文字列から変換できる: IntFromString)
型クラス in Scala 3: 型クラスを満たすことを要求
(例: 「Aが文字列から変換できる」ことを要求する関数: fromReverseString)
型クラスを満たすことを要求するときは、using句を用いてコンテキスト引数を要求する形で表現する
型クラス in Scala 3: 呼び出し
コンテキスト引数が要求される関数を呼び出すコード
→ Scalaコンパイラはterm inferenceによってコンテキスト引数にgivenによる標準的なインスタンスを暗黙に与える
型クラス in Scala 3: 呼び出し
コンテキスト引数が要求される関数を呼び出すコード
→ Scalaコンパイラはterm inferenceによってコンテキスト引数にgivenによる標準的なインスタンスを暗黙に与える
※Scala 2の頃はこの暗黙(implicit)という機構にフォーカスした言語デザインだった
導入: 型クラス in Rust
右下に比較用にScala 3のコード
型クラス in Rust: 定義
型クラスは専用の trait 構文を用いて定義する
※Rustのtraitは必ず暗黙にSelfという型パラメタを含む�(Scalaの例の型パラメタA相当、FromString[A]を実装するオブジェクトとは無関係)
型クラス in Rust: インスタンスの実装
型クラスのインスタンスは専用の構文 impl .. for .. で与える
※i32は整数型�※impl .. for ..自体がgiven相当
型クラス in Rust: 型クラスを満たすことを要求
型クラスを満たすことを要求するときは、where句で制約を明示する
型クラス in Rust: 呼び出し
※Scalaと異なり、明示的に型クラスのインスタンスを与える構文は存在しない (理由は後述)
二つの言語の「型クラス」
構文は色々異なるが (特にRustはSelfを優遇した構文となっている)、�共に「型クラス」として以下のようなことを実現している:
二つの言語の「型クラス」の差異
ある言語機能の呼称やその実態は、言語によって様々
RustとScalaの型クラスは何が異なるのか
二つの言語の「型クラス」の差異
ある言語機能の呼称やその実態は、言語によって様々
RustとScalaの型クラスは何が異なるのか�→ 色々異なる (それはそう)� 色々異なるが、注目すべき言語デザインに根差した違いがある
違い(1): インスタンスのユニーク性
違い (1): インスタンスのユニーク性
Scalaのgivenインスタンスはスコープにローカルなもの
違い (1): インスタンスのユニーク性
Scalaのgivenインスタンスはスコープにローカルなもの
Rustのtrait implはグローバルにユニークなもの
Rustはインスタンスがユニークである →
FromStringの実装はAから一意
i32: FromStringの実装は高々一つ
ユニークであることを前提とした構文
Scalaはときにインスタンスを選べる
「整数」の「順序付け」の方法は複数ある
Scalaはときにインスタンスを選べる
常に明示的に渡す
Scalaはときにインスタンスを選べる
常に明示的に渡す
必要に応じて明示的に渡す
Scalaはときにインスタンスを選べる
常に明示的に渡す
必要に応じて明示的に渡す
Rustの場合は振る舞いを切り替えるには新しい型が必要
違い (2): ランタイムの戦略
違い (2): ランタイムの戦略
Scalaの型クラスのインスタンスはオブジェクト
違い (2): ランタイムの戦略
Scalaの型クラスのインスタンスはオブジェクト
Rustの型クラスのインスタンスを示す値や型は基本的に無い
違い (2): ランタイムの戦略
型クラスのインスタンスのメソッド呼び出しはデフォルトで動的ディスパッチの形であり、そのオーバーヘッドについては処理系の最適化に期待する
違い (2): ランタイムの戦略
型クラスのインスタンスのメソッド呼び出しはデフォルトで動的ディスパッチの形であり、そのオーバーヘッドについては処理系の最適化に期待する
Aに対する型クラスのインスタンスのメソッドはコンパイル時に一意に定まり、そのメソッドの呼び出しに静的に展開される
Rustはランタイムのコストがかかり得る場所が明示的であることを好む:�動的ディスパッチを伴う言語機能としてTrait Objectが提供されている
まとめ
ともに「型クラス」と呼ばれる言語機能でも、言語の目的・指向の違いからデザインや実装戦略に差異があり面白い
共に型システムが強力とされる言語だが、それぞれの特性を理解してコードに反映していきたい / 使い分けていきたい
終
以下おまけ
おまけ: Scalaへの直訳が少し難しい例
おまけ: Scalaへの直訳が少し難しい例
この辺うまい書き方があれば知りたい