Scala 3のgeneric numeric literalsのサンプル

公式に書いてある以上の情報はあまり書かないと思うのですが、一応のサンプルと、細かい罠や不満点があったので書いておきます。

https://docs.scala-lang.org/scala3/reference/experimental/numeric-literals.html

まだ実験的機能で、いつ正式な機能になるのか、そもそも正式な機能になるのか?がこれ書いている2024年12月時点では一切決まっていないと思われる機能です。

外部のライブラリを使うにしても、使わないにしても

「数値を表現するのだけれど、標準のIntやLongより範囲が狭い、あるいは広い」

型を作ったり使ったりする場合がたまにあるかもしれません。ありますよね? (例としてIntやLongと言ったが、割と任意な数値に関連する型でよい)

公式の例ではBigFloatという大きいFloatが例になっていました。

一番よくある例の1つとしては、unsigned int、正の値だけ表すInt、というのがあると思います。

それの実際の表現はcase classだったりopaque typeだったりするわけですが、そこの違いは今回あまり関係ないので触れません。

タイトルというか機能名でなんとなくわかると思いますが、それを表現するのに直接リテラル的な機能を提供しよう、というのがこの実験的機能です。

つまり、場合によって余計なimportなどが一切なしに

val x: PosInt = 2

というように、PosIntが求められている箇所にIntのリテラルを渡したら、勝手にIntのリテラルをPosIntのリテラルとして扱ってくれる。ということです。

これはまた

val x: PosInt = -3 // compile error

という、compile時にチェックされる機能も提供されます。

このcompile時のvalidation含めた機能を使うためには、例えば以下のように FromDigits のtype classのinstanceをinline defで実装します。

compilerのphaseのタイミングの問題なのか、実装する際の書き方が面倒なので注意してください。

また、なぜわざわざこんな機能を新規に提供しようと検討してるのか?の説明をしておきます。

そもそもimplicit conversionとmacroを使えば、見た目上は同等のことがScala 2でも3でもある意味では表現可能というか、以下のようなrefined系のライブラリでは実際にimplicit conversionとして提供しています。

しかし、理想的なScala 3の世界においては、そもそもimplicit conversionに限らずimplicitをすべて撲滅してgivenやusingやextensionやその他に置き換えたいはずなので、implicit conversion以外の手段が欲しいわけです。

implicitに変換する以外の手法としては、(generic numeric literalsがないなら)結局どんな形であれexplicitに変換するしかないですが、例えば

PosInt(2)

2.posInt

posInt"2"

というように、普通のメソッド呼び出しなのか、extensionで後置なのか、string interpolationなのか?の詳細はなんでもいいですが、何か明示しないといけません。 それの中身がmacroでcompile時チェックされるのかどうか?に関わらず。

これはこれで呼び出し箇所少ないなら許容範囲かもしれませんが

「やっぱりもっと便利で汎用的な仕組み欲しいよね!!!」

という気持ちにより検討されている機能だと思われます。

(作成途中の議論を読んでないので、半分以上自分の想像ですが、多分あってる、はず)

それはそれとして大まかな思想というかお気持ちはわかるのですが、実装する際の罠というか注意点が面倒だし、簡単な実装だとしても絶対quotes出てきて、inlineだけで済まないのが微妙だと思うのですが、これ今後改善されるのでしょうかね???

gist.github.com