面倒なことはScalaスクリプトにやらせよう / let scala scripts do the troublesome things

面倒なことはScalaスクリプトにやらせよう / let scala scripts do the troublesome things

2019/09/13 Scala秋祭り

917c89046a8ba98f398ea16b7b335779?s=128

Takumi Kadowaki

September 16, 2019
Tweet

Transcript

  1. 2.

    どなた? Takumi Kadowaki Twitter: @blac_k_ey GitHub: NomadBlacky セプテーニ・オリジナル PYXISチーム所属 広告運用自動化ツールのバックエンド開発が主な仕事

    海と温泉とテトリスが好き ScalaMatsuri2019で頒布した「技術読本」に 「ServerlessFramework + Scala」なアプリケーションについて記事を書き ました
  2. 4.

    DockerHubからあるイメージのタグ一覧をAPIから取得して、 最もサイズの小さいものと大きいものを出力してください { "count": 257, "page": 1, "page_size": 25, "results":

    [ { "id": 2255500, "name": "stable", "last_updated": "2019-09-12T14:51:49.113501Z", "full_size": 50665062 }, { "id": 19451, "name": "latest", "last_updated": "2019-09-12T14:51:29.159817Z", "full_size": 50665667 }, ... ] } https://hub.docker.com/api/content/v1/repositories/public/library/nginx/tags
  3. 5.

    シェルスクリプトで複雑なことをするとつらい… $ curl \ https://hub.docker.com/api/.../nginx/tags \ | jq .results[].full_size ...

    (ここで考えるのをやめる) (我々は jq コマンドのプロではない…) (ページングとか考えるとさらにめんどい…) とりあえずシェルスクリプトで…
  4. 7.

    小さいプログラムを書くにはオーバー… • build.sbt ◦ ビルド定義 ◦ 依存解決 • build.properties ◦

    sbtバージョンの指定 ◦ プラグイン (sbt-assembly etc.) • src/main/scala/.../Hoge.scala ◦ 目的のコード
  5. 14.

    http://ammonite.io/#Ammonite-REPL 何はともあれ、まずはインストールしてみよう (Linuxなど ※執筆時の最新版) $ sudo sh -c '(echo "#!/usr/bin/env

    sh" && curl -L https://github.com/lihaoyi/Ammonite/releases/download/1.6.9/2.13-1.6.9) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' && amm コマンドをひとつ実行するだけ! (Macのかた) $ brew install ammonite-repl
  6. 16.

    Ammonite-REPLの便利な機能 • シンタックスハイライト • 複数行のコード編集 • Undo & Redo •

    Magic Import • browse ◦ 大きな配列など、文字列表現が長い評価結果をページャで確認 • source ◦ REPL上でソースコードを閲覧
  7. 17.

    Magic Import Maven Central からJarを取得して、すぐにライブラリを使える! import $ivy.`<group_id>::<artifact_id>:<version>` // cats の例

    import $ivy.`org.typelevel::cats-core:2.0.0` // sbt との比較 libraryDependencies += "org.typelevel" %% "cats-core" % "2.0.0"
  8. 18.
  9. 24.

    Scalaコードをコマンドラインから直接コンパイル・実行! sample.sc println("Hello Ammonite!!") $ amm sample.sc Compiling (synthetic)/ammonite/predef/DefaultPredef.sc Compiling

    /home/blacky/sample.sc Hello Ammonite!! $ amm sample.sc Hello Ammonite!! 2回目以降はコンパイルしないので実行が早い !
  10. 28.

    デフォルトで使える便利なライブラリたち • ammonite-ops / os-lib ◦ https://github.com/lihaoyi/os-lib ◦ ファイル操作、サブプロセス実行などを提供 •

    requests ◦ https://github.com/lihaoyi/requests-scala ◦ HTTPクライアント • upickle ◦ https://github.com/lihaoyi/upickle ◦ シリアライゼーションライブラリ ◦ MsgPack / JSON に対応
  11. 32.

    GitLab CI での実行ログ ./amm esa-stats-to-datadog.sc --team septeni-original Get septeni-original.esa.io stats

    ... { "members": 55, "posts": 298, "posts_wip": 51, "posts_shipped": 247, "comments": 157, "stars": 197, "daily_active_users": 1, "weekly_active_users": 13, "monthly_active_users": 26 } Post stats to Datadog ... Done!
  12. 35.
  13. 37.

    参考資料 • Document ◦ http://ammonite.io/ • GitHub ◦ https://github.com/lihaoyi/ammonite •

    Blog ◦ http://www.lihaoyi.com • Video ◦ https://vimeo.com/148552858 • IntelliJ IDEA + AmmoniteでScalaスクリプトの開発を始めよう ◦ 弊社エンジニアブログ ◦ http://labs.septeni.co.jp/entry/2018/12/20/120000
  14. 38.

    まとめ • 便利なREPLとしてのAmmonite ◦ ちょっとScalaを書いて試したい時にAmmoniteを! ◦ 特に「Magic Import」が便利! • ScalaスクリプトとしてのAmmonite

    ◦ シェルスクリプトだと面倒、sbtだとオーバーという時の選択肢に! ◦ ざっくり書いてもコンパイラが怒ってくれる ▪ (ちょっとしたテスト代わり) ◦ Java/Scalaの資産が使える! ◦ 何よりScalaでスクリプトを書ける!! ▪ 楽しい!!!!
  15. 39.

    ちなみに: 最初の問題の回答 import upickle.default._ case class Response(page: Int, page_size: Int,

    results: Seq[Tag]) case class Tag(name: String, full_size: Long) implicit val responseReader: Reader[Response] = macroR implicit val tagReader: Reader[Tag] = macroR def fetchAllTags(image: String): Seq[Tag] = Stream.from(1).map { page => read[Response]( requests.get( url = s"https://hub.docker.com/api/content/v1/repositories/public/library/$image/tags", params = Map("page" -> page.toString) ).text ) }.takeWhile(r => r.page < r.page_size) .flatMap(_.results) @main def main(image: String): Unit = { val allTags = fetchAllTags(image) println(allTags.minBy(_.full_size)) println(allTags.maxBy(_.full_size)) }