Scala 3、Pythonのようにインデントベースの構文で書けるようになるってよ!
ここ数年でインデントベースの記述は広くプログラマ界隈で受け入れられるようになってきました。プログラミング言語ではPythonの成功が大きく、ドキュメントではmarkdownとyamlが広く普及しています。そしてScala 3でもとうとうその波に乗ろうという動きが見えてきました・・・
(2019年9月28日追記・更新: 追記内容はここを見てください)
(2019年11月16日追記・更新: 追記内容はここを見てください)
TL;DR
- Scala 3のリサーチコンパイラであるDotty 0.18.1-RC1にインデントベースの構文が実装されました
- Dotty 0.19.0-RC1の変更に合わせて修正しました
- Dotty 0.20.0-RC1の変更に合わせて修正しました
- インデントベースの構文はまだ提案段階でありScala 3の正式な仕様に決定したわけではありません
- 今後機能が変化したり、機能が採用されなかったりする可能性も十分あります
- というか反対意見の方が多いです
- 従来の括弧ベースの構文も混ぜて使えます
- この記事はインデントベース構文の紹介記事です。この構文の良し悪しについては触れていません
まずはコードを御覧ください
ウソみたいだろ・・・Scalaなんだぜ、それ
1 |
|
いつもはこんなコードを書いていたはず・・・
従来のコード
1 |
|
コードの解説
Pythonでインデントを用いてブロックを作る場合は改行の前に「:」が付きますが、Scalaの場合はもっと多くのキーワードがインデント構文の開始の合図になり得ます。
:
=
=>
<-
if
then
else
while
do
try
catch
finally
for
yield
match
また、class
, object
, given
, や enum
定義でもインデント構文を利用できます。
インデントはタブとスペースの両方が使えますが混ぜると比較ができないケースがあるので、混ぜるな危険です。
定義
以下はobject
の定義とenum
の定義でインデント構文を開始しています。
1 |
|
enum
はScala 3(Dotty)の機能でScala 2にはありません。気になる方は「Enumerations」を参照してください。
また、定義の行末を以下のようにwith
で終わることもできます。
1 |
|
with
はオプションですが、インデント構文を使う場合は、基本的につけたほうが良さそうです。例えば、以下のようにクラスBの定義で次の行にインデントをつけ忘れた場合、with
があるとコンパイラが構文エラーにしてくれます。
もし、with
をつけていなかった場合はエラーにはならずに、def
から始まる関数定義はクラスBに所属するのではなくクラスBと同じ名前空間のメソッドとして定義されてしまいます。
1 |
|
if
式
if
式の場合は、インデントブロック開始の合図が「then
」になります。最初then
を書き忘れたらコンパイラにthen
がないって怒られて、then
ってなんぞや???となりました。どうやら新しいキーワードみたいです。
行末のthen
は0.20.0-RC-1でオプションになりました。つまり以下のthen
がなくても正しい構文になります。
1 |
|
if
式で括弧を用いる場合はthen
は必要ありません。
1 |
|
for
式
以下のコードには=
とfor
とyield
がインデント構文の開始の合図になっています。
1 |
|
match
式
match
式は少し特殊でインデントを付けても付けなくてもいいことになっています。つまり以下の2つの書き方が許容されます。
1 |
|
1 |
|
match
以外にはcatch
も同様に次のcase
のインデントは自由です。
ラムダ式
ラムダ式の記号(=>
)もインデント構文の開始の合図になっています。
1 |
|
エンドマーカー
インデントだとブロックの終わりがわかりにくい場合には、エンドマーカーを使ってブロックの終わりを明示することができます。以下のコードでは「end fromString
」が識別子のエンドマーカーです。エンドマーカーはオプションなのでなくても問題なく動作しますが、ドキュメントではひと目でブロックを識別することが難しい長いブロック(20行以上)で使うことを推奨しています。
1 |
|
エンドマーカーは以下の予約語と合わせて用いることもできます。
if
while
for
match
try
new
上記の例では「end try
」がエンドマーカーになっています。
インデントマーカー:
次はPythonっぽい、行末コロン(:
)の例です。開き中括弧({
)が有効な箇所で行末をコロンにするとインデント構文を開始することができます。以下の例ではmap
の後にインデントマーカー:
が付いています。
1 |
|
ただし、このインデントマーカーはまだ他のインデントスキームよりも議論の余地が大きく、コンパイラオプションに-Yindent-colons
を指定した時だけ有効になります。
設定と書換え
インデント構文はデフォルトで有効ですが、コンパイラオプション-noindent
, -old-syntax
,-language:Scala2
のいずれかを指定すれば無効にできます。実際に試してみましたがインデント構文を利用している箇所はエラーになってコンパイルができなくなりました。
また、コンパイラオプションでインデント構文への書換えもできます。インデント構文への書換えは-rewrite
-new-syntax
オプションをつけてコンパイル後に、もう一度-rewrite
-indent
オプションをつけてコンパイルする必要があります。つまり面倒ですが2回コンパイラを起動する必要があります。この書換えは上手くいきました。
逆方向の書換えを行うには-rewrite
-noindent
オプションをつけてコンパイル後に、もう一度を-rewrite
-old-syntax
つけてコンパイルします。この書換えは0.19.0-RC1
時点では失敗しました。どうやらエンドマーカーの書換えで失敗しているようです。
@main
関数
インデント構文とは欠片も関係ありませんが、0.18.1-RC1で導入された@main
関数についても紹介しておきます。従来は以下のように書いていたmain関数ですが^1、
1 |
|
@main
関数を使うと以下のように書くことができます。Scalaのスクリプティングが捗りそうです^2。
1 |
|
今すぐ試してみたい!
上記のサンプルコードをすぐに試せるようにGitHubに公開したのでご査収ください。インデントベースの構文と従来のブレースベースの構文はどちらも有効なので、実際に触ってみて感触を掴むのが一番だと思われます。念のため書いておきますが、サンプルコード自体に特に意味はありません。インデントベースの構文の雰囲気が分かるように適当に構文を並べただけです。
インデントベース構文の状況
インデントベースへの変更は実は2017年にOdersky先生が#2491で提案されていて、このときは大激論の末に一旦クローズされています^3。そしてようやく今回執念のプルリク(#7083)を投げて、捩じ込みました。
「捩じ込む」と表現したのは今回も当然のように大激論が起こったからです。見た目が違うから拡張子変えた方がいいという意見や、読みにくさや曖昧さを指摘する意見や、初学者の混乱を指摘する意見まで様々です。
Odersky先生もこのプルリクはデータを集めるための実験としており、現在のところこのインデント構文はScala 3の新機能の中でも採用される可能性が最も低いものの一つだと考えられます。以下は#7083のリアクションですが、こんなに嫌われているプルリクには滅多にお目にかかれません(笑)。
経緯はともかくOdersky先生の壮大な実験は始まりました。Scalaのようにある程度普及した言語が途中からインデントベースの構文をサポートした例を自分は知りません。今後の成り行きが気になるところですが、Scala 3.0の機能凍結及びScala 3.0のM1リリースは今年(2019年)の秋で、Scala 3.0 finalのリリースは来年(2020年)秋と言われているので[^4]、何か言いたいことがある方はなるべく早めに本家にフィードバックをかけた方が良いと思われます[^5]。
ちなみに本記事はあくまでインデントベース構文の紹介が目的なので、この構文の良し悪しについて突っ込んだ批評は控えておきます。ただ一応参考までに個人的な初見の感想を述べておくと、自分が趣味で使うのには良さそうな感じです。仕事で使うのにはコーディング規約をどうするか悩みどころが多いと感じましたが、結局はエディタ、IDE、フォーマッタ、リンタ等のツール群のサポートがきちんと得られれば使えるとは思うので、それらの対応次第かなと思っています。
[^4]: A Tour of Scala 3を参照。
[^5]: フィードバックの方法としてはScala Contributorsでトピックを立てるのが一番簡単だと思われます。もちろん問題が明確ならイシューを開いたりプルリクを送る方法もあります。
最後に
本記事ではScala 3へ入る可能性のあるインデントベースの構文を紹介しました。本機能は実際にScala 3に入るかどうかはわかりませんが、まだ多くの議論の余地があり、より多くのフィードバックが必要とされています。興味のある方は実際に触ってみてScala Contributors等でフィードバックしてみてはいかがでしょうか?
参考文献
- Announcing Dotty 0.18.1-RC1 – switch to the 2.13 standard library, indentation-based syntax and other experiments
- Announcing Dotty 0.19.0-RC1 – further refinements of the syntax and the migration to 2.13.1 standard library
- Allow significant indentation syntax by odersky · Pull Request #7083 · lampepfl/dotty
- Change indentation rules to allow copy-paste by odersky · Pull Request #7114 · lampepfl/dotty
- Consider syntax with significant indentation · Issue #2491 · lampepfl/dotty
- Optional Braces
更新内容
2019年9月28日の更新内容
先日発表されたDotty 0.19.0-RC1でインデント構文が若干変更されました。
- クラスやオブジェクトの定義でインデントをする場合に
:
が必要なくなった - インデントマーカー
:
の利用時には-Yindent-colons
のオプションの指定が必要になった
上記の内容は本文にも反映済みです。またインデント構文への書換えも試してみたので追記を行っています。
下記のサンプルリポジトリに関しても0.19.0-RC1にバージョンアップして対応済みです。
2019年11月16日の更新内容
先日発表されたDotty 0.20.0-RC1でインデント構文が若干変更されました。
インデントに関係ある変更は以下のとおりです。関連する箇所に関して記事の追記を行っています。
- クラス、トレイトの後ろにオプションで
with
を置けるようになった if
式の行末のthen
がオプションになった