Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

(初心者向け)Scalaでsbtプロジェクトを作るときの勘所

この記事では、Scala初心者がプロジェクトを作るときに分かりにくい(であろう)ポイントを埋めようというものです。

こんな記事を読んだ。

trap.jp

普段Rustを書いている人がScalaを書いてみたという内容の記事で、普段Scalaに振れていない人の視点があってとても良かった。普段我々はScalaを書きすぎているので、初心者がどこでハマるのか?初学者はどのようなポイントを見ているのか?といったことがわからないのだ。

そこでこの記事では、上掲の記事を参考にしつつ、普段自分がScalaプロジェクトを作るときどのようにしているかを説明しようと思う。

プロジェクトを作る

Scalaのプロジェクトを作りビルドするにはsbtというツールを使う*1。これがなんでもお世話してくれる。

最低限必要なもの

sbtでビルドするからにはsbtが必要になる。sbtをインストールする方法は割となんでもいい(後述する理由により、バージョンは勝手に良い感じになるからだ)。OSのパッケージマネージャにも入っているし、brewを使ってもよい。いちおうScala公式としては、Scala関連の諸々のツールを管理してくれるCoursierを使ってほしそうだが、割となんでもいい。

get-coursier.io

sbtはJVM上で動作するので、JDKが必要になる。これは好きなツールでインストールして良いと思う。知人はSDKMAN!を使っていることが多いようだが、asdfでもmiseでもnixでもなんでもいい。

sdkman.io

Scala自体、JDKのバージョンに開発が大きく左右されることは(少なくとも初心者のうちは)ほぼないと思う。最新を入れとけばいいです、くらいの気持ち。

sbtはどこからどこまでやってくれるのか

そもそもsbtの立ち位置だが、sbtは以下の事柄を管理してくれるビルドツールだ。

  • Scalaコンパイラのバージョン管理
  • ライブラリ依存性の管理
  • sbt自体のバージョン管理(!?)

上掲の記事の通り、Rustでいうとrustupcargoを合体させた感じに近い。どのバージョンのScalaを使いたいかを書いておくと、勝手にそのコンパイラを用意してくれるのだ。言い換えると、システムワイドなScalaのバージョン、システムワイドなsbtのバージョンという概念は存在せず、sbtに設定するとプロジェクト単位でビルドに必要なキットが降ってくる(そしてキャッシュだけがユーザレベルで使い回される)という仕組みになっている。

ちなみにsbtのバージョン管理も必要ない仕組みになっていて、システムに入っているsbtがまずproject/build.propertiesを見て、適当なバージョンのsbtを改めて起動するという仕組みになっている。このときsbtのバイナリなどが不足していれば勝手にダウンロードしてキャッシュしてくれる。

プロジェクト作成

プロジェクトを作るには1から手で作ってもいいが、sbt newを実行するとテンプレート一覧が提示され、好きなテンプレートでプロジェクトを掘ってくれる。sbt new テンプレートを指定すると、そのテンプレートを使うことができる。このへんは他の言語とあまり大差ない。

ちなみにsbt new テンプレート -o .を実行すると、ディレクトリを掘らず、今いるディレクトリ直下にテンプレートを展開してくれるので覚えておくと便利かもしれない。

テンプレートの中身は単なるGitHub上のg8テンプレートなので、個人が作ったテンプレートを使ったり、自分でテンプレートを作ることもできる。

その他エコシステムの諸々(コンパイラ、フォーマッタなど)

コンパイラ

前述した通り、sbtはScalaコンパイラのバージョンも管理してくれるので、手でコンパイラを入れる必要はまったくない。我々がやるべきことは、Scalaバージョンを指定することだけだ:

// build.sbt
lazy val root = project
  .in(file("."))
  .settings(
    // ...
    scalaVersion := "3.4.1",
    // ...
  )

やや初心者キラーなのだが、sbtの設定ファイルはScalaである。settingsってところにscalaVersion := "バージョン"って書くとだけ覚えておけばよい。テンプレートでプロジェクトを作成した場合は最初から入っています。

LSP

ScalaのLSPサーバとしてデファクトの地位を占めているのがMetalsだ。というかほぼ標準である。

scalameta.org

実はMetalsを直接インストールすることはほぼ無い。VSCodeでScala拡張機能をインストールしたり、NeovimでScala用のLSP拡張を入れたりすると勝手にダウンロードされて動くようになる。

Metalsはビルドした結果などの中間ファイルを作成するので、.gitignore.metals.bloopといったディレクトリを除外するとよい。上掲記事で出力にビックリしていたが、sbtは多段コンパイルする(build.sbtもまたScalaコードであるため)のでこういうディレクトリ構造になる。targetディレクトリはコンパイラ等の出力用ディレクトリなので、これも.gitignoreに入れると良い。

Formatter / Linter

scalafmt

Scalaのフォーマッタとしてデファクトの地位を占めているのがscalafmtだ。あらゆるプロジェクトで採用されており、これも実質的に標準である。

scalameta.org

基本これ以外にないくらいに思っておくと良いだろう。嬉しいことに、ほぼデフォルトで適切に動作する。

上掲の記事ではscalafmtを手でインストールしている様子だったが、実はVSCodeにScala用の拡張機能をインストールしておくと、勝手にscalafmtがダウンロードされてフォーマット機能として呼び出せるようになるので、明示的に扱いたい場合を除いてはインストールしなくてもよい。

また、Scalaがメジャーバージョンやマイナーバージョンによって若干記法が追加・削除されたのに合わせてscalafmtもプロファイルを変更してこれに追従する方針になっており、これをrunner.dialectという設定で指定する。Scala 2.13系を使っているならscala213を、Scala 3系を使っているならscala3を指定すればよい。ちなみにscala213source3みたいなやつはバージョンを上げたい時用のマイグレーション用プロファイルです。えら〜い

設定ファイルがHOCONなのは、なんか昔からのScalaの風習です。周辺のScalaユーザを見ているとHOCONに困っている人はいなそうなので、慣れですな・・・

Scalaはバージョンごとにdialectと呼ばれるほどの差異があるっぽいですね。 dialectと表現してるのはscalafmtだけかも?

dialectと表現してるのはscalafmtだけですね。ただ2.12、2.13、そして3系で大きめの構文の差異があるのは事実です。3シリーズが登場してから結構時間が経って、ライブラリ対応もだいたい終わっているので、これからは3系を選んでおけばよさそうです。

scalafix

実はあまりscalafix使ったことがない(scalafmtでだいたい良い感じになるため)。とはいえ導入方法は公式ドキュメントの通りで、上掲の記事でも普通に導入できてそう。

Scala 3について

scala3 a.k.a. dotty!? わけわからん... というわけでとりあえず除外。

古い名前が残ってて初心者をビックリさせている!Scala 3でぜんぜん大丈夫です。

ちょっと詳しい説明をすると、実装の話題になるんですが、Scala 3はScala 2コンパイラの改良版ではなく、新たに1からScala 3コンパイラというものを作って、既存のScalaの構文をほぼ踏襲しつつ、新機能を追加した、という立ち位置のコンパイラなんですね。この全く新規のコンパイラのコードネームがDottyでしたが、もうScala 3は実験的な立ち位置ではなく普通にリリースされたので、敢えてDottyと呼ぶことはあまりなくなっていますね。コンパイラ実装のことを示すときに使われるくらいで、普段はScala 3と言っています。

ということで採用: scala212

まったくおすすめしません。 古代文明です。バックポート以外は更新されない状態になっているはずで、新規に採用する理由はほぼないです。すみやかに2.13か3にマイグレーションすることが推奨されています。もうScala 2.12系の提供を打ち切っているライブラリもたくさんあるので、3系にすることをおすすめします。また、先程も書いた通り、Scalaコンパイラのバージョンはsbtが勝手に管理するのでNixで管理する必要はないです。

困り事

その他困りごと マクロが使えない

たぶんScala 2.12を使っているためです。

scala-native-packager動いてない

たぶんScala 2.12を使っているためです。

nixでパッケージビルド

Nixぜんぜん分からない!!!!!! 助けてくれ!!!!!!

と言いつつこのくらいのアプリケーションだと普通にScala Nativeでシングルバイナリにビルドしたりできそうですね。

なんかレスポンスが遅い

メモリ足りてないか、Scala 2.12だからかなあ〜〜

export JAVA_OPTS=-Xmx 4Gくらいしてメモリをたくさんあげるとよいかもしれません。

まとめ

ぜんぜん初心者向けではなさそうな記事になっちゃった。とはいえ、以下の点だけ抑えておくと良さそう:

  • JDKとsbtさえ入れれば後は全部管理される
  • VSCodeのScala拡張を入れるのが一番面倒がない(LSPやscalafmtが自動で入る)
  • 今からやるならScala 3がおすすめです

宣伝

こんな調子でScalaまわりの記事を投稿しているので、読者になっていただけるとお得です。

また、ScalaわいわいランドというScalaの日本語公式Discordコミュニティを運営しています。ブログのヘッダーから入れるのでぜひご活用ください。

参考記事

blog.3qe.us

*1:millというツールもあるが、これはまだ初心者向けではない

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?