Scala3に入るかもしれないContextual Abstractionsを味見してみた(更新・追記あり)
Scala3のリサーチコンパイラであるDottyにImplicitsに代わる「Contextual Abstractions」と呼ばれる一連の機能が実装されていたので一部を味見してみました。
(記事の本文は2020年2月8日時点のDotty最新版と整合するように更新されています。更新の詳細はここを見てくださいを見てください。)
TL;DR
- この記事はDottyに実装されたImplicitsに代わる「Contextual Abstractions」と呼ばれる一連の機能を味見してみたものです
利用したDottyのバージョンは2019年2月時点で最新の0.13.0-RC1です。Dottyの開発は非常に活発なので異なるバージョンでは本記事の内容とは異なる場合があります2019年6月時点で最新の0.16.0-RC3で変更があった文法の更新を反映しました。Dottyの開発は非常に活発なので異なるバージョンでは本記事の内容とは異なる場合があります2019年9月時点で最新の0.18.1-RC1に更新しました。Dottyの開発は非常に活発なので異なるバージョンでは本記事の内容とは異なる場合があります2019年9月時点で最新の0.19.0-RC1に更新しました。Dottyの開発は非常に活発なので異なるバージョンでは本記事の内容とは異なる場合があります- 2020年2月時点で最新の0.19.0-RC1に更新しました。Dottyの開発は非常に活発なので異なるバージョンでは本記事の内容とは異なる場合があります
- 「Contextual Abstractions」は従来のImplicitsで初学者が躓きそうな機能を整理して使いやすくしています
- 「Contextual Abstractions」には従来のImplicitsでは実現できなかった機能(暗黙のインポート、型クラス導出、コンテキストクエリ等)も含まれています
- 「Contextual Abstractions」の機能はまだ提案段階でありScala3の正式な仕様に決定したわけではありません
- 今後機能が変化したり、機能が採用されなかったりする可能性も十分あります
- 「Contextual Abstractions」がScala3に正式採用された場合、古いImplicitsは段階的に廃止される予定です
- 「Contextual Abstractions」への移行はScalafixでサポートされる予定です
- この記事で紹介したサンプルコードは以下のリポジトリにあります。
Scala3とは
2020年初頭に出ることが予定されているScalaの次世代版です。コンパイラの高速化と大幅な機能強化が行われる予定です。基本的には現行のScala(Scala2)とのソースコードレベルの後方互換性を意識して機能強化が行われていますが^1、非互換が生まれるところや推奨の書き方が変わる所はScalfix[^2]で対応することが予定されています。
[^2]: ScalafixはScalaにおける汎用的はリファクタリング、リンティングツールです。Scala3専用ではありません。
Dottyとは
Dotty^3はScala3の研究用コンパイラで、Scala3の仕様や実装を研究するためのものです。Scala3はDottyがベースになることがアナウンスされています[^4]。
[^4]: Dottyに現在入っている機能が全てScala3に取り込まれるわけではありません。Scala3に正式に取り込まれる機能は Scala Improvement Process (SIP)(JavaのJCP/JSRのようなもの)を通過する必要があり、ここで承認が得られなければScala3に取り込まれることはありません。2019年2月時点ではDottyの新機能の多くはまだSIP以前の提案段階であり、これから徐々にSIPのレビュープロセスに乗せられていくものと予想されます。
Contextual Abstractionsとは
現行のScalaには俗にImplicitsと呼ばれる機能がありますが、初学者を非常に混乱させる機能として悪名高いものでした^5。そこでDottyには、この混乱に決着を着けるべくImplicitsの機能を包含しつつより整理された「Contextual Abstractions」と呼ばれる一連の機能が実装されました[^6]。Implicitsの代替という面でみるとこれらの機能は「implicit」というキーワードをなるべく使わずに別の用語(given
等)で置き換えて、型クラスをより書きやすいようにチューニングしたような内容になっている印象です。本記事ではDottyドキュメント[^7]を参考にしながら、「Contextual Abstractions」の機能の一部を味見してみました。以下が味見した機能の一覧です[^8]。
given
インスタンス(Given Instances)- 従来の
implicit
で定義されていたインスタンスと同等です
- 従来の
using
節(Using Clauses)- 従来の
implicit
で定義されていたパラメータリストと同等です
- 従来の
given
インポート(Given Imports)- 通常のimportでは
given
で定義されたgiven
インスタンスはインポートされず、別途import A.given
でインポートする必要があります import A.{given, _}
でパッケージAのgiven
インスタンスも含めた全てをインポートできますgiven
インスタンスがどこから来たのかを明確にするために導入されたようです
- 通常のimportでは
- 拡張メソッド(Extension Methods)
- Dottyの新機能です
- 型が定義された後にメソッドを追加することができます
- 型クラスの実装(Implementing Typeclasses)
- 「
given
インスタンス」、「using
節」、「拡張メソッド」でよりシンプルに型クラスが実装可能になりました
- 「
[^6]: 暗黙のインスタンスと推論可能パラメータが追加された経緯を知りたい方は#5458と #5852をご確認ください・・・#5458の方は長すぎてまともに追っていませんが元々はwitness
というキーワードで提案されて途中でinstance
に変わって#5825でimplied
に変わったようです。本当に大激論で互換性に対する懸念が何回も強く出ています。とりあえずこの機能はSIPを通さないとScala3に入ることはないという念押しでマージされました。それ以外のContextual Abstractionsの機能(拡張メソッドや型クラスの導出等)はここまでもめた様子はなかったです。さらに#6649
でdelegate
に変更されました。そしてさらに、#6773と#7210で大幅に文法チェンジ!!delegate
が排除されてgiven
一色になりました・・・ 本当に何回変わるんだろう・・・ツライ・・・
[^7]: このドキュメントは最新版のスナップショットなので、どんどん書き換えられています。今の所過去のバージョンは参照できないみたいです・・・
[^8]: 機能の日本語訳は自分がしました。間違っていたら教えてください。
味見の方法
ここから`dotty-0.22.0-RC1.zip`をダウンロードして解凍します。解凍後のフォルダの`bin`にパスを通せば利用できるようになります。dotc
がコンパイラです。dotr
はクラス名を指定するとコンパイル済みのバイナリを実行します。単独で起動した場合にはREPLになります。
1 |
|
もしくはサンプルコードをGitHubで公開したので、sbt
をすでにインストールされている方はそちらの方が早いと思います。使い方はREADME.md
をご覧ください
味見の結果
Dottyドキュメントに記載されている例をベースに味見をしてみました^9。
基本的な例(拡張メソッド)
拡張メソッドは既存の型にメソッドを注入できる機能です。ポイントは「継承」を用いずにアドホックにメソッド拡張を行える点です。
拡張の仕方は3通りあって、直接、型にメソッドを拡張する方法と、トレイトを用いて拡張メソッドを定義したあとに、given
インスタンスで注入する方法とextension
キーワードを使って拡張メソッドの定義と注入を一緒に済ませてしまう方法です。
1 |
|
以下のように利用します。
1 |
|
少し高度な例(given
インスタンス、using
節)
前述のとおりgiven
インスタンスは型を拡張するときに用いることができますが、その応用として型パラメータを持つ型を「共通の型」として他の型を拡張できます。この機能は「型クラス」とも呼ばれますが詳細は次節で説明します。
using
節ではスコープに存在するgiven
インスタンスを受け取ることができます。仮にusing
節で指定された変数の型にマッチするgiven
インスタンスがスコープに存在すれば、呼び出し時にusing
節を省略することができます。
以下の例ではInt
型とList
型をOrd
型で拡張しています。また、using
節を用いてmax
関数やmaximam
関数等を定義しています。
1 |
|
given
インスタンスの利用例が以下になります。
1 |
|
高度な例(型クラス)
型クラスは単一の型を拡張するわけではなく「共通の型」を用意して「共通の型」を後から既存の型にアドホックに注入して拡張できる点が異なります。すでにコードで紹介した例を用いて型クラスとそうでない拡張を区別すると以下のようになります。
- 単一の型を拡張(型クラスではない)
String
型を拡張してlongestStrings
メソッドを追加
- 共通の型で複数の型を拡張(型クラス)
- Ord型クラスを定義
- 実態は型パラメータを持つトレイトで複数の型で共通で使えるメソッド
compare
と演算子<
、>
を持っている
- 実態は型パラメータを持つトレイトで複数の型で共通で使えるメソッド
given
インスタンスを用いてInt
型をOrd
型クラスで拡張(intOrd
)given
インスタンスを用いてList
型をOrd
型クラスで拡張(listOrd
)
- Ord型クラスを定義
以下の例ではプログラミングで有用な型クラスとして半群(Semigroup
)、モノイド(Monoid
)、関手(Functor
)、モナド(Monad
)を定義しています。また、型クラスのgiven
インスタンスも定義しています。
1 |
|
型クラスは以下のように利用できます。
1 |
|
Contextual Abstractionsのその他の機能
Contextual Abstractionsの機能で味見できなかった機能を簡単に紹介します。
- マルチバーサル等価性(Multiversal Equality)
- Scala2では文字列と数値が比較可能でしたが、この機能により厳密に型が合っていないとコンパイルエラーにすることもできるようになりました
- 型クラスの導出(Typeclass Derivation)
- Haskellの
deriving
と同等の機能です。現在のDottyで導出可能な型クラスはマルチバーサル等価性を表すEql
のみのようです - メタプログラミングを使えば自分で導出可能な型クラスの定義もできます
- Haskellの
- 暗黙の型変換(implicit conversion)
- 元々のImplicitsのトラブルメーカーです。新たに
Conversion
という型が定義されてその暗黙のインスタンスを定義することで利用できます
- 元々のImplicitsのトラブルメーカーです。新たに
- コンテキスト関数(Context Functions)
- 以前はImplicit Function Typeと呼ばれていた機能で0.13.0-RC-1で構文が変更されました
- ビルダーパターンを簡単に実装できます
味見してみた感想
Implicitsが大分飼いならされたような印象でした。特に従来はimplicitをパラメータリストで受け取っていたのをgiven
という専用構文で受け取るようになったのが非常に分かりやすかったです。ただ、従来のimplicitly
の名称はまだかなり揺れているみたいです[^10]。
もともとは「A Snippet of Dotty」を読んで、あまりにも自分が知っているScalaと違っていたので調べ始めたのがこの記事を書こうと思ったきっかけです。この記事がScala3がどういう方向を目指しているのか知りたい人の参考になれば幸いです。
[^10]: もともとsummon
という名前で提案されていましたが、0.13.0-RC-1
ではinfer
に変わり、#5893ではthe
に変更されています。しかし、#7205でまさかのsummon
の復活!! 追うのも楽じゃない・・・
追記・更新内容
2019年3月10日の更新内容
本家のブログが公開されたようです。`0.13.0-RC-1`のタグが打たれてから10日以上経ってからの公開なのでかなり遅い方だと思いますが、それだけ今回のリリースが盛りだくさんだったと言うことだと思います。本家のブログには従来の`implicit`がなぜダメだったのか丁寧に説明されていました。The implicit keyword is used for both implicit conversions and conditional implicit values and we identified that their semantic differences must be communicated more clearly syntactically. Furthermore, the implicit keyword is ascribed too many overloaded meanings in the language (implicit vals, defs, objects, parameters). For instance, a newcomer can easily confuse the two examples above, although they demonstrate completely different things, a typeclass instance is an implicit object or val if unconditional and an implicit def with implicit parameters if conditional; arguably all of them are surprisingly similar (syntactically). Another consideration is that the implicit keyword annotates a whole parameter section instead of a single parameter, and passing an argument to an implicit parameter looks like a regular application. This is problematic because it can create confusion regarding what parameter gets passed in a call. Last but not least, sometimes implicit parameters are merely propagated in nested function calls and not used at all, so giving names to implicit parameters is often redundant and only adds noise to a function signature.
意訳すると従来のimplicit
にはimplicit conversions
とconditional implicit values
の2つの用途があったけど、意味が違うし初学者は混同しやすいので構文的に別にするという話です。というかconditional implicit values
という言い方は自分は初めて目にしました。単純なimplicit values
よりもわかりやすいですね。
この本家のブログを受けてというわけではないですが、前回の記事でサンプルの解説が大分雑だったのでいろいろと見直して、サンプルコードもGitHubに公開しました。興味のある方は味見をして頂けると幸いです。
2019年6月22日の更新内容
先日発表されたDotty 0.16.0-RC3も0.16.0-RC3にしてあります。あと何回キーワードが変更されるんだろう・・・
2019年9月15日の更新内容
先日発表されたDotty 0.18.1-RC1でリーダーモナドの例がコンパイルできるようになっていました。また、0.18.1-RC1で追加されたインデントベースの構文についても記事を書いたので興味があればご一読ください。
2019年9月28日の更新内容
先日発表されたDotty 0.19.0-RC1で本記事に関する文法が大きく変更されました。関連するプルリクは主に以下の4つです。
- Trial: given as instead of delegate for by odersky · Pull Request #6773 · lampepfl/dotty
- Change to new given syntax by odersky · Pull Request #7210 · lampepfl/dotty
- Drop old syntax styles for givens by odersky · Pull Request #7245 · lampepfl/dotty
- Replace the[...] by summon[...] by odersky · Pull Request #7205 · lampepfl/dotty
簡単に言うとdelegate
がgiven
に置き換えられてgiven
節がgiven
パラメータになってsummon
大復活です。0.19.0-RC1より前のものも含まれていますが、今回合わせて修正しました。
2020年2月8日の更新内容
Dottyは0.21.0でめでたくfeature-complete
しました。つまりこれ以降は大きな機能追加はないはずです。 ・・・と安心していたら0.22.0でusing
キーワードが追加されました。文法の調整はまだ続くようです・・・
今回は以下の3つのリリースで行われた修正を反映しています。
- Announcing Dotty 0.20.0-RC1 – `with` starting indentation blocks, inline given specializations and more
- Announcing Dotty 0.21.0-RC1 - explicit nulls, new syntax for `match` and conditional givens, and more
- Announcing Dotty 0.22.0-RC1 - syntactic enhancements, type-level arithmetic and more
大きな変更はusing
、on
、as
、extension
などのキーワードが登場してより読みやすくなったことだと思います。実際の使い方は記事本文をご覧ください。
あと、今まで拡張メソッドはこの記事では説明がなかったので追加しました。