Slide 1

Slide 1 text

Scala.jsͰAndroidͱυϝΠ ϯ૚Λڞ༗͠Α͏ Ѩ઒ ߞ࢘ @atty303 AI tech studio / CyberAgent, Inc. © 2019 CyberAgent, Inc. 1/35

Slide 2

Slide 2 text

Ѩ઒ ߞ࢘ ςοΫϦʔυ @ൢଅαΠωʔδAd • ۀ຿ྺ 14 ೥ / Scala ྺ 5 ೥ • ήʔϜ޷͖ (Steam ϝΠϯ) ࠷ۙ͸ࣨ಺αΠΫϦϯάΛؤ ுͬͯ·͢ɻ © 2019 CyberAgent, Inc. 2/35

Slide 3

Slide 3 text

ൢଅαΠωʔδAd © 2019 CyberAgent, Inc. 3/35

Slide 4

Slide 4 text

© 2019 CyberAgent, Inc. 4/35

Slide 5

Slide 5 text

৽͍͠ސ٬ମݧͱ࣮ళ ฮͷՁ஋Λ૑଄͢Δ Λϛογϣϯʹܝ͛ɺίϯϏχ ΤϯεετΞɾυϥοάετ Ξɾεʔύʔͱ͍࣮ͬͨళฮͷ σδλϧԽɾϝσΟΞԽΛɺσ δλϧαΠωʔδͱίϯςϯπ ʗ޿ࠂ഑৴γεςϜΛ༻͍࣮ͯ ݱ͠·͢ɻ © 2019 CyberAgent, Inc. 5/35

Slide 6

Slide 6 text

αΠωʔδͷٕज़ελοΫ • Android 6.0 (API 23) • Kotlin • MediaPlayer + TextureView • WebView + Scala.js © 2019 CyberAgent, Inc. 6/35

Slide 7

Slide 7 text

ϨΠϠʔͱٕज़ • Kotlin ͸υϥΠό૚ͷΈ • υϝΠϯ͓ΑͼΞϓϦέʔ γϣϯ૚͸ Scala.js © 2019 CyberAgent, Inc. 7/35

Slide 8

Slide 8 text

Kotlin/Scalaͷίʔυྔ Kotlin $ wc -l `find context/signage/ -name '*.kt'` 1815 total Scala $ wc -l `find context/signage/ -name '*.scala'` 6515 total © 2019 CyberAgent, Inc. 8/35

Slide 9

Slide 9 text

Scala.js © 2019 CyberAgent, Inc. 9/35

Slide 10

Slide 10 text

Scala.js • Scala ͔Β JavaScript ʹίϯύΠϧ͢Δ΍ͭ • 2013೥͔Β։ൃ͕ଓ͍͍ͯΔ • ࠷ۙ 1.0-RC ͕ग़ͯɺ2020೥1݄ʹ 1.0 ϦϦʔε༧ ఆ © 2019 CyberAgent, Inc. 10/35

Slide 11

Slide 11 text

ݸਓతͳҹ৅ • ScalaͰJSॻ͚Δͷ͸໘ന͍ • JSΤίγεςϜͱͷ૬ੑΛߟ͑ͨΒTypeScript͕ ແ೉Ͱ͸ʁ • มԽͷܹ͍͠ React, Vue, ... ͳͲΛ࢖͑Δͷ͔ • ࢖͍ॴ͕೉ͦ͠͏ © 2019 CyberAgent, Inc. 11/35

Slide 12

Slide 12 text

ࠓճ࠾༻ͨ͠ཧ༝ • υϝΠϯ૚ΛαʔόʔͱαΠωʔδͰڞ༗ͨ͠ ͔ͬͨ • αʔόʔ͸ Scala Ͱ֬ఆɺαΠωʔδ͸ Android Ͱ֬ఆɺͱ͍͏ঢ়گ • Android ΑΓ(׳Ε͍ͯΔ) Web ٕज़ʹد͔ͤͨͬ ͨ → Scala.js ͕ϕετϚονͰ͸ʂʁ © 2019 CyberAgent, Inc. 12/35

Slide 13

Slide 13 text

Scala.js ͷಋೖ • project/plugins.sbt ʹ Scala.js ϓϥάΠϯΛ௥Ճ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.31") // JVM/JS ΫϩεϏϧυ͕ඞཁͳ৔߹͸Լه΋௥Ճ addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.1") © 2019 CyberAgent, Inc. 13/35

Slide 14

Slide 14 text

Scala.js ͷಋೖ • ϓϩδΣΫτઃఆͰ enablePlugins(ScalaJSPlugin) Λ௥Ճ͢Δ lazy val signageDriverSignageJs = (project in file("context/signage/driver/signage-js")) .dependsOn(signageApplication.js) .enablePlugins(ScalaJSPlugin) .settings(...) © 2019 CyberAgent, Inc. 14/35

Slide 15

Slide 15 text

Scala.js ͷಋೖ • sbt Ͱ fastOptJS λεΫΛ࣮ߦ͢Δͱ js ͕ॻ͖ग़ ͞ΕΔ $ sbt fastOptJS Ҏ্ɺ؆୯Ͱ͢Ͷ © 2019 CyberAgent, Inc. 15/35

Slide 16

Slide 16 text

Scala.jsಋೖ·Ͱ • औΓֻ͔͔ͬͯΒ 2 ೔ఔ౓Ͱ׬ྃ • ΋ͱ΋ͱ Scala.js ͷ͜ͱΛҰ੾ߟྀ͍ͯ͠ͳ͍ ίʔυϕʔε • υϝΠϯ૚͸ϥΠϒϥϦґଘ͕গͳ͔ͬͨ © 2019 CyberAgent, Inc. 16/35

Slide 17

Slide 17 text

AndroidͱScala.jsͷܨ͗ © 2019 CyberAgent, Inc. 17/35

Slide 18

Slide 18 text

interop KotlinͷυϥΠό૚ͱScala.jsͷΞϓϦέʔγϣϯ૚ Λܨ͙ύʔπ © 2019 CyberAgent, Inc. 18/35

Slide 19

Slide 19 text

Scala.js ଆ import scala.scalajs.js import scala.scalajs.js.annotation.JSGlobal @js.native @JSGlobal object interop extends js.Object { def requestCacheContents(urls: js.Array[String]): Unit = js.native def publish( dataBase64: String, method: String, endpoint: String, headers: String ): Unit = js.native def loadSlot( index: Int, src: String, poster: String, isImage: Boolean, invertedSlotRotation: Int ): Unit = js.native def flipSlot(index: Int): Unit = js.native } © 2019 CyberAgent, Inc. 19/35

Slide 20

Slide 20 text

Androidଆ import android.webkit.JavascriptInterface import android.webkit.WebView class WebViewInterop( val requestCacheContentsDelegate: RequestCacheContentsDelegate // publish ... ) { fun addToWebView(webView: WebView): WebView { webView.addJavascriptInterface(this, "interop") return webView } @JavascriptInterface fun requestCacheContents(urls: Array): Unit { requestCacheContentsDelegate.apply(urls) } // publish ... } © 2019 CyberAgent, Inc. 20/35

Slide 21

Slide 21 text

υϝΠϯ૚ͷશମ૾ © 2019 CyberAgent, Inc. 21/35

Slide 22

Slide 22 text

SignageProgram ͜ΕΛαʔόʔͰ૊ΈཱͯɺAndroidଆͰར༻͢Δͷ ͕ओ໨త • Protocol Buffers Ͱαʔόʔ͔ΒAndroid΁ૹ৴ • Scala ͰγϦΞϥΠζͯ͠ɺScala.js ͰσγϦΞϥ Πζ • ྆ଆͱ΋ ScalaPB Ͱ؆୯ʹͰ͖ͨ © 2019 CyberAgent, Inc. 22/35

Slide 23

Slide 23 text

Scala.js ར༻ͷ޻෉ © 2019 CyberAgent, Inc. 23/35

Slide 24

Slide 24 text

Long ܕͷར༻ʹؾΛ͚ͭΔ • JVM ͷ Long ͸ 63bit • JS ͷ Number ͸ Double ૬౰ͳͷͰԾ਺෦ 52bit • LocalTime#toNanoOfDay() Ͱਫ਼౓མͪʹૺ۰ • ࣮ߦ࣌ΤϥʔʹͳΔ • toSecondOfDay() + getNano() ͰରԠ © 2019 CyberAgent, Inc. 24/35

Slide 25

Slide 25 text

Java API ͸جૅతͳ෦෼ͷΈར༻Մ ೳ • ৄࡉ͸ https://github.com/scala-js/scala-js/tree/ master/javalib/src/main/scala/java Λࢀর © 2019 CyberAgent, Inc. 25/35

Slide 26

Slide 26 text

Java API Ͱࠓճࠔͬͨͱ͜Ζ • java.time ύοέʔδ͕σϑΥϧτͰ͸࢖͑ͳ͍ • "com.zoepepper" %%% "scalajs-jsjoda-as-java- time" ͷར༻Ͱղܾ © 2019 CyberAgent, Inc. 26/35

Slide 27

Slide 27 text

Scala.js ͱ࢖͍ͬͯΔϥΠϒϥϦ • ScalaPB • "org.typelevel" %%% "cats-core" • "org.typelevel" %%% "cats-effect" • "com.beachape" %%% "enumeratum" • "io.estatico" %%% "newtype" • "eu.timepit" %%% "refined" • "io.lemonlabs" %%% "scala-uri" © 2019 CyberAgent, Inc. 27/35

Slide 28

Slide 28 text

UseCase Λ JVM/JS Ͱڞ༗͢Δ • DisplayContentUseCase ͸ JVM/JS Ͱڞ༗͍ͨ͠ • JS Ͱ͸αΠωʔδʹίϯςϯπΛදࣔ͢Δ • JVM Ͱ͸දࣔ͞ΕΔίϯςϯπΛϓϨϏϡʔ͢ Δ • UseCase ͕ڞ௨ͳͷͰ LocationRepository ͳͲ ͷ Repository ΋ JVM ͱ JS ͰಉҰ trait Λ࢖͍ͨ © 2019 CyberAgent, Inc. 28/35

Slide 29

Slide 29 text

࠷ॳͷܗ trait LocationRepository { def findById(id: LocationId): ConnectionIO[Option[Location]] } • doobie.free.connection.ConnectionIO • doobie (JDBC ϥΠϒϥϦ) ͷ JS ൛͸ଘࡏ͠ͳ ͍(౰વ) • JS ͔Β ConnectionIO ܕΛར༻Ͱ͖ͳ͍ • ͳΜΒ͔ͷந৅Խ͕ඞཁ © 2019 CyberAgent, Inc. 29/35

Slide 30

Slide 30 text

Repositoryͷந৅Խͨ͠ܗ trait LocationRepository[F[_]] { def findById(id: LocationId): F[Option[Location]] } // JVM trait PostgresLocationRepository extends LocationRepository[ConnectionIO] { def findById(id: LocationId): ConnectionIO[Option[Location]] } // JS trait InMemoryLocationRepository extends LocationRepository[IO] { def findById(id: LocationId): IO[Option[Location]] } © 2019 CyberAgent, Inc. 30/35

Slide 31

Slide 31 text

Transactor΋ந৅Խ trait Transactor[M[_], N[_]] { def trans(implicit ev: Bracket[N, Throwable]): M ~> N } // Doobie class DoobieTransactor[N[_]]( underlying: doobie.util.transactor.Transactor[N] ) extends Transactor[ConnectionIO, N] { override def trans(implicit ev: Bracket[N, Throwable]): ConnectionIO ~> N = underlying.trans } // JS class RawTransactor[N[_]]( implicit liftIO: LiftIO[N] ) extends Transactor[IO, N] { override def trans(implicit ev: Bracket[N, Throwable]): IO ~> N = LiftIO.liftK[N] } © 2019 CyberAgent, Inc. 31/35

Slide 32

Slide 32 text

རศੑͷͨΊʹ Transactable ܕΫϥ εΛఆٛ trait Transactable[M[_]] { def transact[A](transactor: Transactor[M, IO])(m: M[A])(implicit ev: Bracket[IO, Throwable]): IO[A] } // Doobie implicit lazy val transactableForConnectionIO: Transactable[ConnectionIO] = new Transactable[ConnectionIO] { override def transact[A]( transactor: repository.Transactor[ConnectionIO, IO] )(m: ConnectionIO[A])(implicit ev: Bracket[IO, Throwable]): IO[A] = transactor.trans.apply(m) } // JS implicit lazy val transactable: Transactable[IO] = new Transactable[IO] { override def transact[A](transactor: Transactor[IO, IO])(m: IO[A])(implicit ev: Bracket[IO, Throwable]): IO[A] = transactor.trans.apply(m) } © 2019 CyberAgent, Inc. 32/35

Slide 33

Slide 33 text

UseCase͔Βར༻͢Δ class DisplayContentUseCase[F[_]]( transactor: Resource[IO, Transactor[F, IO]], locationRepository: LocationRepository[F] )( implicit bracketForF: Bracket[F, Throwable], transactableForF: Transactable[F] ) { def apply(locationId: LocationId): IO[Unit] = { val transIo: IO[Location] = transactor.use { xa => val io = for { maybeLocation <- locationRepository.findById(locationId) location <- maybeLocation.liftTo[F](new LocationNotFountError) } yield location Transactable[F].transact(xa)(io) } } for { location <- transIo // ... } yield () } © 2019 CyberAgent, Inc. 33/35

Slide 34

Slide 34 text

UseCaseͷڞ༗ʹ͍ͭͯ • UseCaseͷڞ༗͸ͪΐͬͱ΍Γ͔͗͢΋͠Εͳ͍ • F[_] ͕͍ͬͺ͍ͰΑ͘Θ͔Βͳ͍ʁ • ࣮ࡍ໰୊ɺJVM/JS ͰͦΕͧΕͰUseCase͸ඍົ ʹҧ͏ • ແཧʹڞ௨ԽͤͣɺJVM/JS ͰUseCaseΛ෼͚Δ͔ ΋ © 2019 CyberAgent, Inc. 34/35

Slide 35

Slide 35 text

Scala.js ಋೖͷॴײ • Scala.js ࣗମ͸׬શʹ 1.0 ΫΦϦςΟ͸͋Δ • पลϥΠϒϥϦ΋ॆ࣮ (Pure Scala ͳϥΠϒϥϦ͸ ΄΅࢖͑Δ) • ࠓճͷΑ͏ʹ༻్͕ϋϚΕ͹͘͢͝༗༻ • Ͱ΋WebϑϩϯτΤϯυΛScala.jsͰॻ͜͏ͱ͸· ͩࢥΘͳ͍ © 2019 CyberAgent, Inc. 35/35