Slide 1

Slide 1 text

面倒なことは Scalaスクリプトにやらせよう 2019/09/16 Scala秋祭り Takumi Kadowaki @blac_k_ey

Slide 2

Slide 2 text

どなた? Takumi Kadowaki Twitter: @blac_k_ey GitHub: NomadBlacky セプテーニ・オリジナル PYXISチーム所属 広告運用自動化ツールのバックエンド開発が主な仕事 海と温泉とテトリスが好き ScalaMatsuri2019で頒布した「技術読本」に 「ServerlessFramework + Scala」なアプリケーションについて記事を書き ました

Slide 3

Slide 3 text

突然ですが問題です

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

シェルスクリプトで複雑なことをするとつらい… $ curl \ https://hub.docker.com/api/.../nginx/tags \ | jq .results[].full_size ... (ここで考えるのをやめる) (我々は jq コマンドのプロではない…) (ページングとか考えるとさらにめんどい…) とりあえずシェルスクリプトで…

Slide 6

Slide 6 text

みんな大好きScala! みんな大好きsbt!

Slide 7

Slide 7 text

小さいプログラムを書くにはオーバー… ● build.sbt ○ ビルド定義 ○ 依存解決 ● build.properties ○ sbtバージョンの指定 ○ プラグイン (sbt-assembly etc.) ● src/main/scala/.../Hoge.scala ○ 目的のコード

Slide 8

Slide 8 text

世の中には色々なスクリプト言語がある JavaScript Ruby Python Groovy PHP Perl etc...

Slide 9

Slide 9 text

それでもやっぱりScalaで書きたい〜 JavaScript Ruby Python Groovy PHP Perl

Slide 10

Slide 10 text

この間を埋めるものはないだろうか… コード量/プロジェクト規模 大 小 Shell Script

Slide 11

Slide 11 text

そんなあなたに、 Ammonite 大 小 @ コード量/プロジェクト規模 Shell Script

Slide 12

Slide 12 text

Ammoniteとは? https://ammonite.io lihaoyi/Ammonite ● Scala REPL ● Script Runner ● File System Library ● Shell これらの総称

Slide 13

Slide 13 text

Ammonite-REPL

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Hello World!!

Slide 16

Slide 16 text

Ammonite-REPLの便利な機能 ● シンタックスハイライト ● 複数行のコード編集 ● Undo & Redo ● Magic Import ● browse ○ 大きな配列など、文字列表現が長い評価結果をページャで確認 ● source ○ REPL上でソースコードを閲覧

Slide 17

Slide 17 text

Magic Import Maven Central からJarを取得して、すぐにライブラリを使える! import $ivy.`:::` // cats の例 import $ivy.`org.typelevel::cats-core:2.0.0` // sbt との比較 libraryDependencies += "org.typelevel" %% "cats-core" % "2.0.0"

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Ammonite-Script

Slide 20

Slide 20 text

Scalaコードをコマンドラインから直接コンパイル・実行! sample.sc println("Hello Ammonite!!")

Slide 21

Slide 21 text

Scalaコードをコマンドラインから直接コンパイル・実行! sample.sc println("Hello Ammonite!!") Scalaスクリプトでよく使われる拡張子

Slide 22

Slide 22 text

Scalaコードをコマンドラインから直接コンパイル・実行! sample.sc println("Hello Ammonite!!") $ amm sample.sc amm コマンドの後ろにスクリプトのパ ス

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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回目以降はコンパイルしないので実行が早い !

Slide 25

Slide 25 text

Magic Importとの合わせ技 sample_magic.sc import $ivy.`org.jsoup:jsoup:1.12.1` val doc = org.jsoup.Jsoup.connect("https://www.google.com").get println(doc.getElementsByTag("title")) $ amm sample_magic.sc Google もちろんスクリプトでも Magic Importが使える!

Slide 26

Slide 26 text

Ammoniteに含まれるライブラリ依存

Slide 27

Slide 27 text

デフォルトで使える便利なライブラリたち Ammoniteに含まれている依存はMagicImportをせずとも使えます。 その中でもスクリプトの実装に役立つライブラリを一部紹介します。 $ coursier resolve com.lihaoyi:ammonite_2.13.0:1.6.9 ... com.lihaoyi:ammonite-ops_2.13:1.6.9:default com.lihaoyi:os-lib_2.13:0.3.0:default com.lihaoyi:requests_2.13:0.2.0:default com.lihaoyi:upickle_2.13:0.7.5:default ... ...

Slide 28

Slide 28 text

デフォルトで使える便利なライブラリたち ● 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 に対応

Slide 29

Slide 29 text

実際の活用例

Slide 30

Slide 30 text

esa.ioの利用状況をDatadogで可視化してみる Stats取得 メトリクス送信 GitLab CI の Scheduled Jobで 1時間に1回実行

Slide 31

Slide 31 text

ソースコード スライドには収まりきらなかったのでgistで… https://gist.github.com/NomadBlacky/2c56814a1993e327ed2c2e3d 8da01eed

Slide 32

Slide 32 text

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!

Slide 33

Slide 33 text

あとはダッシュボードを作ってあげるだけ

Slide 34

Slide 34 text

Scala製のDatadog APIクライアントを作りました! Starください!!! https://github.com/NomadBlacky/scaladog (宣伝)

Slide 35

Slide 35 text

そのほか… ● Twitterの「いいね」から画像を取得する ○ https://github.com/NomadBlacky/ammonite-scripts/blob/master/bin/get-twitter-images.sc ● 何度閉じてもダイアログが出てくるScalaスクリプト ○ https://gist.github.com/NomadBlacky/a70d76f5d699994da83c92775935f3f6 ○ 実行は自己責任でお願いします ● ScalaからAWS CDKを叩いてインフラを構築する ○ http://nomadblacky.hatenablog.com/entry/2019/07/14/194607 ○ Scalaでインフラを作る!

Slide 36

Slide 36 text

おわりに

Slide 37

Slide 37 text

参考資料 ● 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

Slide 38

Slide 38 text

まとめ ● 便利なREPLとしてのAmmonite ○ ちょっとScalaを書いて試したい時にAmmoniteを! ○ 特に「Magic Import」が便利! ● ScalaスクリプトとしてのAmmonite ○ シェルスクリプトだと面倒、sbtだとオーバーという時の選択肢に! ○ ざっくり書いてもコンパイラが怒ってくれる ■ (ちょっとしたテスト代わり) ○ Java/Scalaの資産が使える! ○ 何よりScalaでスクリプトを書ける!! ■ 楽しい!!!!

Slide 39

Slide 39 text

ちなみに: 最初の問題の回答 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)) }