Scala native ことはじめ
Scala 3 のリリースがひと段落したおかげか Scala native の開発が盛んになってきているようなので scala native を試してみる. 2021年8月時点の最新安定版は4.0.
graal native と異なり細かいセットアップをしなくてもイイ感じに ネイティブファイルを生成できる場合が多い.
セットアップ
Scala native をコンパイルするには sbt と Java 8+に加えて llvm, clang と clang++ が必要.
sbt と java は coursierでインストールできる.
curl -fLo cs https://git.io/coursier-cli-"$(uname | tr LD ld)"
chmod +x cs
./cs install
rm ./cs
cs setup
c 言語のコンパイラには gcc, msvc(microsoft visual c/c++), clang などがあるが、 clang は gcc の後継に位置するコンパイラである.
llvm による最適化や gcc と比べてより詳細な警告・エラーを表示してくれる利点がある.
ubuntu では以下のコマンドで最新のllvmとclang, clang++ がインストールされる.
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
または
sudo apt install clang
sudo apt install libgc-dev # optional
mac OS は、以下のコマンドで llvm をインストールすると clang がついてくる. インストールできたら 環境にインストールされている clang と衝突しないように path を通す.
brew install llvm
brew install bdw-gc # optional
詳しくは以下のサイトを参考にされたし.
次に環境変数に CLANG_PATH
, CLANGPP_PATH
を設定する.
export CLANG_PATH=/path/to/clang-<version>
export CLANGPP_PATH=/path/to/clang++-<version>
以下のコマンドでプロジェクトの雛形を作成できる.
sbt new scala-native/scala-native.g8
または以下のようにセットアップする.
.
├── build.sbt
├── project
│ ├── build.properties
│ ├── plugins.sbt
│
├── src
└── main
└── scala
lazy val app = project.in(file("."))
.enablePlugins(ScalaNativePlugin)
.settings(
scalaVersion := "3.2.1"
)
sbt.version = 1.8.0
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.8")
scala native で何ができる?
scala native のユースケースとしては次のようなものが挙げられる.
- CLIツール
- aws lambda/gcp cloud function
- c/c++ で公開されている OS の API(win32apiなど) を利用するアプリケーション
- c/c++ ネイティブに近いパフォーマンスが必要な場合
scala native の何がうれしい?
- vs Java/Scala:
- JVM 立ち上げのもっさりが無くなる. aws lambda/gcp functions での利用が捗る.
- c/c++ を呼び出すときに JNI のようにボイラプレートをたくさん書かなくていい.
- vs Java + graal native:
- Scala をサポートしているので細かい設定が不要.
- vs c/c++:
- コードの記述量が減る. Scala の型や文法など高度に抽象化された機能がそのまま使える.
- vs rust:
- メモリ、ライフタイムや所有権などを気にしなくても良い.
scala native の基礎
ひな型を生成した直後は src/main/scala/Main.scala
は以下のようなコードが書かれているはず.
object Main {
def main(args: Array[String]): Unit =
println("Hello, world!")
}
まずはこれが正しく動くか確かめる.
プロジェクトルートで以下のコマンドを実行すると scala native のコンパイルが走り、生成された実行ファイルが実行される.
sbt run
実行ファイルは target/scala-2.x.x/<project-name>-out
に吐き出される.
もちろんターミナルから./target/scala-2.x.x/<project-name>-out
と呼び出しても動く.
これだけでは何ができるのかわかりにくいので scala-native のパッケージを使って http リクエスト、 json のパース、ファイルの操作を順にみていこう.
メジャーな Scala のライブラリ(scalatest, cats, shapelessなど)は scala native に対応していることが多い.
http リクエストには sttp, json のパースには argonaut, ファイルの操作には oslib を使う.
パッケージ名の区切りに現れる%
が3つになっていることに注意してほしい. scala native のパッケージを使うときはこのように記述する.
libraryDependencies ++= Seq(
"com.softwaremill.sttp.client3" %%% "core" % "3.3.13",
"io.argonaut" %%% "argonaut" % "6.3.6",
"com.lihaoyi" %%% "os-lib" % "0.7.8"
)
まずは適当な json をパースしてみよう.
import argonaut._
object Main {
def main(args: Array[String]): Unit ={
val json ="""{
| "foo": "bar",
| "baz": 1,
| "hoge": {
| "fuga": "piyo"
| }
}
""".stripMargin
println(Parse.parse(json))
}
}
再度 sbt run
を走らせると パースされた結果が出力されるはずだ.
次は http リクエストを扱う.
http リクエストを扱うには sttp を使うのだが、これは curl をバックエンドに使っているので次のライブラリが必要である.
- curl
- libcrypto
- libidn11-dev
sudo apt install libidn11-dev libssl-dev curl
Main.scala を書き換える.
import sttp.client3._
object Main {
def main(args: Array[String]): Unit ={
val query ="scala-native"
val request:Request[Either[String,String],Any] = basicRequest.get(uri"https://api.github.com/search/repositories?q=$query")
val backend:SttpBackend[Identity,Any] = CurlBackend()
val response: Identity[Response[Either[String,String]]] = request.send(backend)
println(response.body)
}
}
再度 sbt run
コマンドを走らせる.
ひょっとすると 次のように環境変数を渡してやる必要があるかもしれない.
STTP_NATIVE=1 sbt run
同様に oslib
をインポートしてファイルを操作するコードを書ける.
import os._
object Main {
def main(args: Array[String]): Unit ={
val example = os.read( os.pwd / "example.txt")
println(example)
// do whatever you want
}
}
他にも cli を作るのに便利な コマンド引数のパーサー decline(https://github.com/bkirwi/decline) や ターミナルに出力される文字列を装飾する fansi(https://github.com/com-lihaoyi/fansi) などのライブラリが scala native に対応している.
scala native の設定
scala native の設定は build.sbt
から指定できる.
import scala.scalanative.build._
nativeConfig ~= {
_.withClang(path/to/clang)
.withClangPP(path/to/clangpp)
.withCompileOptions(Seq())
.withMode(Mode.default) // default is debug
.withGC(GC.default) // default is immix
.withOptimize(true)
}
その他設定項目は https://scala-native.readthedocs.io/en/latest/user/sbt.html#sbt-settings-and-tasks を参照されたし.
executable な cli ツール、チョットしたスクリプトから大規模なアプリケーションまで Scala を使って Scalable なプログラミングを楽しみましょう(^ω^)
Discussion