この記事は Scala Advent Calendar 2019 の6日目です。
こんにちは。最近はPYXISのデータ基盤チームでSREっぽいことしている 門脇(@blac_k_ey)です。
TETRIS99の次はポケモンに進捗を奪われる日々を送っています。
締め切りギリギリまで ポケモンで忙しかった 「これ書きたい!」というテーマが思いつかなかったので、個人的に最近のScala開発でお世話になっているライブラリやツールなどを紹介していこうと思います。
詳細はあまり書かず、「ここが好き!」ぐらいのお気持ち表明ぐらいの文章でしかないので、ゆるく暖かい気持ちで読んでくれたら幸いです。
Ammonite
自分のScala生活が豊かになっている大きな要因のひとつ。
Magic Importsを使って、ライブラリをちょっと試したいときにササッと使えるし、
$ amm Loading... Welcome to the Ammonite Repl 1.7.1 (Scala 2.12.10 Java 1.8.0_181) If you like Ammonite, please support our development at www.patreon.com/lihaoyi @ import $ivy.`com.typesafe.play::play-json:2.7.4` https://repo1.maven.org/maven2/com/typesafe/play/play-json_2.12/2.7.4/play-json_2.12-2.7.4.pom 100.0% [##########] 4.2 KiB (1.0 KiB / s) https://repo1.maven.org/maven2/com/typesafe/play/play-functional_2.12/2.7.4/play-functional_2.12-2.7.4.pom 100.0% [##########] 1.5 KiB (12.0 KiB / s) https://repo1.maven.org/maven2/com/typesafe/play/play-functional_2.12/2.7.4/play-functional_2.12-2.7.4-sources.jar 100.0% [##########] 9.7 KiB (79.4 KiB / s) https://repo1.maven.org/maven2/com/typesafe/play/play-functional_2.12/2.7.4/play-functional_2.12-2.7.4.jar 100.0% [##########] 175.5 KiB (389.2 KiB / s) https://repo1.maven.org/maven2/com/typesafe/play/play-json_2.12/2.7.4/play-json_2.12-2.7.4-sources.jar 100.0% [##########] 52.5 KiB (94.5 KiB / s) https://repo1.maven.org/maven2/com/typesafe/play/play-json_2.12/2.7.4/play-json_2.12-2.7.4.jar 100.0% [##########] 706.6 KiB (705.2 KiB / s) import $ivy.$ @ import play.api.libs.json._ import play.api.libs.json._ @ val json = Json.parse("""{"language": "Scala", "repl": "Ammonite"}""") json: JsValue = JsObject(Map("language" -> JsString("Scala"), "repl" -> JsString("Ammonite")))
スケジュール実行に対応したCIツールを使えば、手元で書いたちょっとしたスクリプトでささっと自動化できるし、
# .gitlab-ci.yml image: openjdk:8 scheduled_post_stats: only: refs: - schedules variables: - $SCHEDULED_JOB_POST_STATS script: - ./amm esa-stats-to-datadog.sc --team septeni-original
何度閉じてもダイアログが開くという犯罪臭のするスクリプトだって書けちゃう!!(?)
import java.awt._, event._ import javax.swing._ import scala.util.Random val (screenCenterW, screenCenterH) = { val ss = Toolkit.getDefaultToolkit().getScreenSize (ss.getWidth.toInt / 2, ss.getHeight.toInt / 2) } val label = new JLabel("何回閉じても無駄ですよ~ww") def randomLocation(): (Int, Int) = (screenCenterW + Random.nextInt(200) - 200, screenCenterH + Random.nextInt(200) - 200) def newDialog(): Unit = { val dialog = new JDialog() dialog.setSize(300, 200) val (x, y) = randomLocation() dialog.setLocation(x, y) dialog.add(label) dialog.setVisible(true) dialog.addWindowListener(new WindowAdapter { override def windowClosing(e: WindowEvent): Unit = { newDialog() } }) } newDialog()
こうしてAmmoniteは自分のScala生活に欠かせないものになっていきました。
気になった人は以下のコマンドを実行して早速インストール! (執筆時点の最新版)
$ sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/lihaoyi/Ammonite/releases/download/1.8.2/2.13-1.8.2) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' && amm
Ammoniteに関してはいくつか資料を公開していますので、そちらもご覧いただけると幸いです。
upickle
JSON, MessagePack に対応したシリアライゼーションライブラリ。
Ammoniteの依存に含まれるため、上記のコマンドでインストールした方はすぐお試しできます。
@ import upickle.default._ import upickle.default._ @ case class Post(id: Int, name: String, tags: Seq[String]) defined class Post @ implicit val postRW: ReadWriter[Post] = macroRW postRW: ReadWriter[Post] = upickle.core.Types$ReadWriter$$anon$3@73fb1d7f @ val json = """ [ {"id":1, "name":"post1", "tags":["Java"]}, {"id":2, "name":"post2", "tags":["Scala", "Ammonite"]} ] """ json: String = """ [ {"id":1, "name":"post1", "tags":["Java"]}, {"id":2, "name":"post2", "tags":["Scala", "Ammonite"]} ] """ @ val posts = read[Seq[Post]](json) posts: Seq[Post] = Vector(Post(1, "post1", Vector("Java")), Post(2, "post2", Vector("Scala", "Ammonite"))) @ write(posts) res5: String = "[{\"id\":1,\"name\":\"post1\",\"tags\":[\"Java\"]},{\"id\":2,\"name\":\"post2\",\"tags\":[\"Scala\",\"Ammonite\"]}]"
macroによって、最低限のボイラープレートでJSONの読み書きを実現できていますね。
他のJSONライブラリと比べて好きなところとしては、自身以外のライブラリに依存していないところでしょうか。
$ coursier resolve --tree com.lihaoyi::upickle:0.8.0 Result: └─ com.lihaoyi:upickle_2.13:0.8.0 ├─ com.lihaoyi:ujson_2.13:0.8.0 │ └─ com.lihaoyi:upickle-core_2.13:0.8.0 │ └─ org.scala-lang.modules:scala-collection-compat_2.13:2.0.0 │ └─ org.scala-lang:scala-library:2.13.0 ├─ com.lihaoyi:upack_2.13:0.8.0 │ └─ com.lihaoyi:upickle-core_2.13:0.8.0 │ └─ org.scala-lang.modules:scala-collection-compat_2.13:2.0.0 │ └─ org.scala-lang:scala-library:2.13.0 └─ com.lihaoyi:upickle-implicits_2.13:0.8.0 └─ com.lihaoyi:upickle-core_2.13:0.8.0 └─ org.scala-lang.modules:scala-collection-compat_2.13:2.0.0 └─ org.scala-lang:scala-library:2.13.0 $ coursier resolve --tree com.typesafe.play::play-json:2.7.4 Result: └─ com.typesafe.play:play-json_2.13:2.7.4 ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.8 ├─ com.fasterxml.jackson.core:jackson-core:2.9.8 ├─ com.fasterxml.jackson.core:jackson-databind:2.9.8 │ ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8 │ └─ com.fasterxml.jackson.core:jackson-core:2.9.8 ├─ com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.8 │ ├─ com.fasterxml.jackson.core:jackson-core:2.9.8 │ └─ com.fasterxml.jackson.core:jackson-databind:2.9.8 │ ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8 │ └─ com.fasterxml.jackson.core:jackson-core:2.9.8 ├─ com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8 │ ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8 │ ├─ com.fasterxml.jackson.core:jackson-core:2.9.8 │ └─ com.fasterxml.jackson.core:jackson-databind:2.9.8 │ ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8 │ └─ com.fasterxml.jackson.core:jackson-core:2.9.8 ├─ com.typesafe.play:play-functional_2.13:2.7.4 │ └─ org.scala-lang:scala-library:2.13.0 ├─ joda-time:joda-time:2.10.1 ├─ org.scala-lang:scala-library:2.13.0 └─ org.scala-lang:scala-reflect:2.13.0 └─ org.scala-lang:scala-library:2.13.0 $ coursier resolve --tree org.json4s::json4s-native:3.6.7 Result: └─ org.json4s:json4s-native_2.13:3.6.7 ├─ org.json4s:json4s-core_2.13:3.6.7 │ ├─ com.thoughtworks.paranamer:paranamer:2.8 │ ├─ org.json4s:json4s-ast_2.13:3.6.7 │ │ └─ org.scala-lang:scala-library:2.13.0 │ ├─ org.json4s:json4s-scalap_2.13:3.6.7 │ │ └─ org.scala-lang:scala-library:2.13.0 │ └─ org.scala-lang:scala-library:2.13.0 └─ org.scala-lang:scala-library:2.13.0 $ coursier resolve --tree io.circe::circe-core:0.11.1 Result: └─ io.circe:circe-core_2.12:0.11.1 ├─ io.circe:circe-numbers_2.12:0.11.1 │ └─ org.scala-lang:scala-library:2.12.8 ├─ org.scala-lang:scala-library:2.12.8 └─ org.typelevel:cats-core_2.12:1.5.0 ├─ org.scala-lang:scala-library:2.12.7 -> 2.12.8 ├─ org.typelevel:cats-kernel_2.12:1.5.0 │ └─ org.scala-lang:scala-library:2.12.7 -> 2.12.8 ├─ org.typelevel:cats-macros_2.12:1.5.0 │ ├─ org.scala-lang:scala-library:2.12.7 -> 2.12.8 │ └─ org.typelevel:machinist_2.12:0.6.6 │ ├─ org.scala-lang:scala-library:2.12.6 -> 2.12.8 │ └─ org.scala-lang:scala-reflect:2.12.6 │ └─ org.scala-lang:scala-library:2.12.6 -> 2.12.8 └─ org.typelevel:machinist_2.12:0.6.6 ├─ org.scala-lang:scala-library:2.12.6 -> 2.12.8 └─ org.scala-lang:scala-reflect:2.12.6 └─ org.scala-lang:scala-library:2.12.6 -> 2.12.8
Scala.js対応している点も人によっては嬉しいかもしれません。
あと、他のJSONライブラリと比較してパフォーマンスが良いそうですが、自身で試してないので深くは言及しません。
requests
とてもシンプルなHTTPクライアント。
PythonのRequestsから影響を受けているみたいです。
これもAmmoniteの依存に含まれます。
@ val response = requests.get("https://google.com") response: requests.Response = Response( "https://www.google.com/", 200, "OK", Map( "expires" -> Buffer("-1"), "server" -> Buffer("gws"), "p3p" -> Buffer("CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\""), "x-xss-protection" -> Buffer("0"), "cache-control" -> Buffer("private, max-age=0"), "date" -> Buffer("Thu, 05 Dec 2019 12:54:14 GMT"), "content-type" -> Buffer("text/html; charset=ISO-8859-1"), "transfer-encoding" -> Buffer("chunked"), "x-frame-options" -> Buffer("SAMEORIGIN"), "alt-svc" -> Buffer( "quic=\":443\"; ma=2592000; v=\"46,43\",h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\... @ response.text.take(100) res1: String = "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/WebPage\" lang=\"ja\"><head><meta content"
import文なしにそのまま書ける点が地味ながらすき。
前述のupickleと合わせればWebAPIを叩くスクリプトも組めますね。
sbt-explicit-dependencies
コンパイル時に不要なライブラリや、暗黙的に依存しているライブラリを見つけてくれるsbtプラグインです。
ここでの「暗黙的に依存している」とは、ライブラリAが依存するライブラリBをアプリケーションコードが直接利用していることを指します。 (例: Circeの依存を追加して、ソースコード上でcatsを利用している)
↓インストール方法↓
// 執筆時点の最新版 addSbtPlugin("com.github.cb372" % "sbt-explicit-dependencies" % "0.2.11")
たとえば、以下のようなプロジェクトがあったとします。
// build.sbt lazy val root = (project in file(".")) .settings( name := "explicitdeps", libraryDependencies ++= Seq( "com.lihaoyi" % "ammonite" % "1.8.2" cross CrossVersion.full ) )
// Hello.scala object Hello extends App { println(ujson.Obj("key" -> "value", "foo" -> "bar").render(indent = 2)) }
sbtを起動して、 unusedCompileDependencies
, undeclaredCompileDependencies
を実行します。
sbt:explicitdeps> unusedCompileDependencies [warn] explicitdeps >>> The following libraries are declared in libraryDependencies but are not needed for compilation: [warn] - "com.lihaoyi" % "ammonite" % "1.8.2" [success] Total time: 0 s, completed Dec 5, 2019 11:28:04 PM sbt:explicitdeps> undeclaredCompileDependencies [warn] explicitdeps >>> The project depends on the following libraries for compilation but they are not declared in libraryDependencies: [warn] - "com.lihaoyi" %% "ujson" % "0.8.0" [success] Total time: 0 s, completed Dec 5, 2019 11:28:17 PM
前者でライブラリ依存に加えているのに使われていない ammonite
の依存が検出され、
後者でアプリケーションコードで使われているのに明示的に依存していない ujson
が検出されました。
これで、闇のライブラリ依存に対する防衛術が手に入りましたね。べんり!
不正な依存があった場合にエラーとなる unusedCompileDependenciesTest
, undeclaredCompileDependencies
コマンドもあるので、CIに組み込むといい感じになります。
check_unused_compile_dependencies: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: '1.8' - name: Check unused compile dependencies run: sbt test/unusedCompileDependenciesTest check_undeclared_compile_dependencies: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: '1.8' - name: Check undeclared compile dependencies run: sbt test/undeclaredCompileDependenciesTest
プロジェクト導入初期からぜひ入れたいプラグインですね!
scala-steward
依存ライブラリのアップデートを探してリポジトリにプルリクエストを投げてくれるとてもかしこいbot。
似たものとしてはdependabotなどがあります。
(こちらはsbtに非対応)
自分で動かすことも可能ですが、今回はコミュニティで定期的に動かしてくれているものを使いましょう。
このファイルに自身が公開しているGitHubリポジトリを加えて、プルリクエストを出します。
そのうち筆者のfthomasさんがマージしてくれます。(感謝)
すると、1日1回ぐらい(?)の頻度でライブラリのアップデートを確認して、自身のリポジトリにプルリクエストを出してくれます。
自分でライブラリのアップデートを確認する作業は骨が折れるので、とても便利ですね!
便利と感じたら、ぜひ感謝の気持ちとしてBadgeを付けましょう。
まとめ
Scalaすき…