この記事はScala Advent Calendar 2018の20日目です。
ブログに初投稿します、エンジニアの門脇(@blac_k_ey)です。
広告運用周りの開発を担当しています。
最近、自分の中でAmmoniteが熱いのでこの度ブログを書いてみることにしました。
今回はIntelliJ IDEAでAmmoniteを使ったScalaスクリプトの開発環境を構築する方法をお伝えします。
Ammoniteとは?
https://ammonite.io/ammonite.io
Ammoniteは、lihaoyiさんが開発した高機能なScalaREPLおよび、Scalaスクリプトの実行環境です。
なぜScalaでスクリプトを書くのか?
これは自身の所感なのですが、以下の利点が上げられます。
…などでしょうか。
まぁ、やっぱり好きな言語で書きたいっていうのが大きいですね(笑)
IntelliJのサポート
IntelliJはAmmoniteを用いたScalaスクリプトの開発をサポートしています。
しかし、上記の記事にある操作を実行しても、依存するライブラリが依存するライブラリの補完が効かなかったり、そもそも import $ivy
を認識してくれない時があったりなど、少々使いにくさが目立ってしまっています。1
今回はsbtを利用してその不便さを回避、快適なScalaスクリプト開発ができる環境を目指します。
前準備
必要なものは以下です。なるべく最新の環境を揃えるのがよいでしょう。
- IntelliJ
- sbt
- Ammonite
この記事を読んでいる方ならIntelliJとsbtはインストールされているかと思いますので割愛します。
Ammoniteの最新版は以下のコマンドでをインストールできます。
(2018年12月時点、v1.5.0)
$ sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/lihaoyi/Ammonite/releases/download/1.5.0/2.12-1.5.0) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' && amm
$ brew install ammonite-repl
インストールが完了したらまずはREPLを立ち上げてみましょう。
$ amm Loading... Welcome to the Ammonite Repl 1.5.0 (Scala 2.12.8 Java 1.8.0_191) If you like Ammonite, please support our development at www.patreon.com/lihaoyi @
※fishを使っている方はうまく動作しないかもしれません。2bashを介して実行するのがよいでしょう。
$ bash -c amm
プロジェクトを作成する
giter8テンプレートを用意しました。
sbt new
コマンドでプロジェクトを作成できます。
$ sbt new NomadBlacky/ammonite-intellij.g8 [info] Loading settings for project global-plugins from idea.sbt,build.sbt ... [info] Loading global plugins from /Users/t_kadowaki/.sbt/1.0/plugins [info] Set current project to scala (in build file:/Users/t_kadowaki/projects/scala/) [info] Set current project to scala (in build file:/Users/t_kadowaki/projects/scala/) A minimal Scala Scripts project. name [Scala Scripts Project]: scala_version [2.12.8]: ammonite_version [1.5.0]: Template applied in /Users/t_kadowaki/projects/scala/./scala-scripts-project $ tree scala-scripts-project/ scala-scripts-project/ ├── build.sbt ├── my-script.sc └── project └── build.properties 1 directory, 3 files
作成されたファイルは以下のようになっているかと思います。
bulid.sbt
// It uses to import dependencies to IntelliJ. lazy val root = (project in file(".")). settings( inThisBuild(List( scalaVersion := "2.12.8" )), libraryDependencies ++= Seq( "com.lihaoyi" % "ammonite" % "1.5.0" cross CrossVersion.full // Please add dependencies to use in Scala Scripts. // "com.sample" %% "scala-library" % "0.1.0" ) )
my-script.sc
// If you want to add a dependency, please add the same one to build.sbt. // import $ivy.`com.sample::scala-library:0.1.0` @main def hello(thing: String = "World"): Unit = { println(s"Hello, $thing!") }
プロジェクトを作成したらIntelliJ上でプロジェクトを開きます。
(sbtプロジェクトとしてインポートされます。)
File
-> Open
-> scala-scripts-project
を選択
プロジェクトを開いたら、Scalaスクリプトの拡張子である .sc
をAmmoniteであることを認識させる必要があります。
(デフォルトはWorksheetとして扱われる)
File
-> Settings
-> Language & Frameworks
-> Scala
-> Worksheet
を
Always Worksheet
-> Always Ammonite
に変更
これで準備完了です!
Hello Ammonite!
まずはテンプレートから展開されたスクリプトを実行してみます。
$ amm my-script.sc Compiling /Users/t_kadowaki/projects/scala/scala-scripts-project/my-script.sc Hello, World! $ amm my-script.sc Hello, World! $ amm my-script.sc --thing Ammonite Hello, Ammonite!
2回実行してみました。
1回目と比べて2回目は Compiling ...
と表示がなく、起動が少し速いことが分かるかと思います。
これはAmmoniteがコンパイルしたスクリプトのバイトコードをキャッシュしているからです。
また、関数の引数である thing: String
がコマンドラインオプションとして渡せるのがわかります。
@main def hello(thing: String = "World"): Unit = { println(s"Hello, $thing!") }
スクリプトに @main
とアノテーションが書かれていますがこれは何でしょうか。
これはスクリプトのエントリーポイントを示しています。
スクリプトを以下のように書き換えてみましょう。
@main def hello(thing: String = "World"): Unit = { println(s"Hello, $thing!") } @main def fizzbuzzJson(i: Int): Unit = { val fizzbuzz = (1 to i).map { i => (i % 3, i % 5) match { case (0, 0) => "FizzBuzz" case (0, _) => "Fizz" case (_, 0) => "Buzz" case _ => i.toString } } val json = ujson.Obj("fizzbuzz" -> fizzbuzz) println(json) }
実行してみます
$ amm my-script.sc Need to specify a subcommand to call when running my-script.sc Available subcommands: hello --thing String (default World) fizzbuzzJson --i Int $ amm my-script.sc fizzbuzzJson 15 {"fizzbuzz":["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz"]}
@main
を複数定義するとサブコマンドのように振る舞うことができます!
fizzbuzzJson
でJSON文字列を作成していますが、これにはuJsonというライブラリを使っています。
Ammoniteにはスクリプトを実装するのに役立つ様々なライブラリが依存関係に含まれています。
- os-lib
- ファイルIO・サブプロセスの実行
- upickle
- シリアライゼーション (上記のuJsonを含んでいる)
- scalaj-http
- HTTPクライアント
…など
スクリプトにライブラリ依存を追加する
import $ivy
を使うとライブラリをスクリプトの依存に含めることが可能です。
ここでは試しにスクレイピングライブラリであるscala-scraperを追加してみます。
import $ivy.`net.ruippeixotog::scala-scraper:2.1.0`
ここでIntelliJが $ivy
を認識してポップアップを出してくれると良いのですが、場合によってはうまく動作しません。3
そこで、sbtの libraryDependencies
に加え、IntelliJでインポートすることでそれを回避します。
lazy val root = (project in file(".")). settings( inThisBuild(List( scalaVersion := "2.12.8" )), libraryDependencies ++= Seq( "com.lihaoyi" % "ammonite" % "1.5.0" cross CrossVersion.full, "net.ruippeixotog" %% "scala-scraper" % "2.1.0" ) )
sbtプロジェクトとしてインポートしたらコードを書いてみましょう
補完がちゃんと効いていますね!
以上、駆け足になってしまいましたが、IntelliJでScalaスクリプトを開発環境を整える方法をお伝えしました。
ぜひ一度、AmmoniteによるScalaスクリプトの威力を感じてみてください。
スクリプトを書く際のTipsや実際の活用例などは、次回以降お伝えできればと思っています。