Scala.jsã¯ãScalaéçºè
ã«ããã³ããšã³ããã¯ãããžãŒã®å·šå€§ãªäžçãéããŸãã éåžžãScala.jsã䜿çšãããããžã§ã¯ãã¯WebãŸãã¯nodejsã¢ããªã±ãŒã·ã§ã³ã§ãããJavaScriptã©ã€ãã©ãªãäœæããã ãã§ããå ŽåããããŸãã
ãã®ãããªScala.jsã©ã€ãã©ãªã®äœæã«ã¯ããã€ãã®åŸ®åŠãªç¹ããããŸãããJSéçºè
ã«ã¯éŠŽæã¿ãããããã§ãã ãã®èšäºã§ã¯ã
Github APIãæäœããããã®ç°¡åãªScala.jsã©ã€ãã©ãªãŒïŒ
ã³ãŒã ïŒãäœæããã€ãã£ãªã JS APIã«çŠç¹ãåœãŠãŸãã
ããããæåã«ããªãããªãã¯ãã®ãããªã©ã€ãã©ãªãäœæããå¿
èŠããããããããªãçç±ãå°ããããšæãã§ããããïŒ ããšãã°ããã§ã«JavaScriptã§èšè¿°ãããã¯ã©ã€ã¢ã³ãã¢ããªã±ãŒã·ã§ã³ããããScalaã®ããã¯ãšã³ããšéä¿¡ããå Žåã
Scala.jsã䜿çšããŠãŒãããäœæã§ãããšã¯èããããŸãããã次ã®ããšãå¯èœã«ããããã³ããšã³ãéçºè
ãšã®å¯Ÿè©±çšã®ã©ã€ãã©ãªãäœæã§ããŸãã
- è€éãŸãã¯éèªæãªã¯ã©ã€ã¢ã³ãåŽã®ããžãã¯ãé ãã䟿å©ãªAPIãæäŸããŸãã
- ã©ã€ãã©ãªã§ã¯ãããã¯ãšã³ãã¢ããªã±ãŒã·ã§ã³ããã¢ãã«ãæäœã§ããŸãã
- å圢ã³ãŒãã¯ãã®ãŸãŸã§äœ¿çšã§ãããããã³ã«åæã®åé¡ãå¿ããããšãã§ããŸãã
- Facebookã®Parseã®ãããªå
¬ééçºè
APIããããŸãã
ãããã®ãã¹ãŠã®å©ç¹ã®ãããã§ãJavascript API SDKã®éçºã«ãæé©ã§ãã
æè¿ãREST JSON APIã«ã¯2ã€ã®ç°ãªããã©ãŠã¶ãŒã¯ã©ã€ã¢ã³ãããããšããäºå®ã«åºäŒããŸããããã®ãããå圢ã©ã€ãã©ãªã®éçºã¯è¯ãéžæã§ããã
ã©ã€ãã©ãªã®äœæãå§ããŸãããèŠä»¶ïŒScalaéçºè
ãšããŠãæ©èœçãªã¹ã¿ã€ã«ã§èšè¿°ãããã¹ãŠã®Scalaãããã䜿çšããããšèããŠããŸãã åæ§ã«ãã©ã€ãã©ãªéçºè
ã¯ãJSéçºè
ã«ãšã£ãŠç解ãããããã®ã§ãªããã°ãªããŸããã
ãã£ã¬ã¯ããªæ§é ããå§ããŸããã ãããã¯ãScalaã¢ããªã±ãŒã·ã§ã³ã®éåžžã®æ§é ãšåãã§ãã
+-- build.sbt +-- project Š +-- build.properties Š L-- plugins.sbt +-- src Š L-- main Š +-- resources Š Š +-- demo.js Š Š L-- index-fastopt.html Š L-- scala L-- version.sbt
resources/index-fastopt.html
ããŒãžã¯APIã確èªããããã«ã©ã€ãã©ãªãš
resources/demo.js
ãã¡ã€ã«ã®ã¿ãããŠã³ããŒãããŸã
APIç®æšã¯ãGithub APIãšã®å¯Ÿè©±ãç°¡çŽ åããããšã§ãã æåã«ããŠãŒã¶ãŒãšãã®ãªããžããªã®ããŒããšãã1ã€ã®æ©èœã®ã¿ãå®è¡ããŸãã ãããã£ãŠãããã¯ãããªãã¯ã¡ãœããã§ãããåççµæãå«ãããã€ãã®ã¢ãã«ã§ãã ã¢ãã«ããå§ããŸãããã
ã¢ãã«ã¯ã©ã¹ã次ã®ããã«å®çŸ©ããŸãã
case class User(name: String, avatarUrl: String, repos: List[Repo]) sealed trait Repo { def name: String def description: String def stargazersCount: Int def homepage: Option[String] } case class Fork(name: String, description: String, stargazersCount: Int, homepage: Option[String]) extends Repo case class Origin(name: String, description: String, stargazersCount: Int, homepage: Option[String], forksCount: Int) extends Repo
è€éãªããšã¯äœããããŸããã
User
ã¯ããã€ãã®ãªããžããªãããããªããžããªã¯ãªãªãžãã«ãŸãã¯ãã©ãŒã¯ã«ããããšãã§ããŸãããJSéçºè
åãã«ããããšã¯ã¹ããŒãããã«ã¯ã©ãããã°ããã§ããïŒ
æ©èœã®è©³çŽ°ã«ã€ããŠã¯ãã Scala.js APIãJavascriptã«ãšã¯ã¹ããŒãããããåç
§ããŠãã ããããªããžã§ã¯ããäœæããããã®APIããããã©ã®ããã«æ©èœããããã³ã³ã¹ãã©ã¯ã¿ããšã¯ã¹ããŒãããç°¡åãªãœãªã¥ãŒã·ã§ã³ãèŠãŠã¿ãŸãããã
@JSExport case class Fork(name: String, /*...*/)]
ãã ããæ©èœããŸããããšã¯ã¹ããŒãããã
Option
ã³ã³ã¹ãã©ã¯ã¿ãŒããªãããã
homepage
ãã©ã¡ãŒã¿ãŒãäœæã§ããŸããã ã±ãŒã¹ã¯ã©ã¹ã«ã¯ä»ã®å¶éããããç¶æ¿ã䜿çšããŠã³ã³ã¹ãã©ã¯ã¿ãŒããšã¯ã¹ããŒãããããšã¯ã§ããŸããããã®ãããªã³ãŒãã¯ã³ã³ãã€ã«ãããŸããã
@JSExport case class A(a: Int) @JSExport case class B(b: Int) extends A(12) @JSExport object Github { @JSExport def createFork(name: String, description: String, stargazersCount: Int, homepage: UndefOr[String]): Fork = Fork(name, description, stargazersCount, homepage.toOption) }
ããã§ã¯ã
js.UndefOr
ã®å©ããåããŠãJSã¹ã¿ã€ã«ã®ãªãã·ã§ã³ã®ãã©ã¡ãŒã¿ãŒãåŠçããŸã
js.UndefOr
ãæž¡ãããšãããŸã£ããè¡ããªãããšãã§ããŸãã
Scalaãªããžã§ã¯ãã®ãã£ãã·ã¥ã«é¢ãã泚æïŒGithub()
æ¯ååŒã³åºãããšã¯ãå§ãã§ããŸãããæ lazãå¿
èŠãªãå Žåã¯ãèµ·åæã«ãã£ãã·ã¥ã§ããŸãã
<!--index-fastopt.html--> <script> var Github = Github()
ãã©ãŒã¯åãååŸããããšãããšã
undefined
ååŸ
undefined
ãŸãã ããã§ãããšã¯ã¹ããŒããããŠããŸãããã¢ãã«ã®ããããã£ããšã¯ã¹ããŒãããŸãããã
String
ã
Boolean
ãŸãã¯
Int
ãªã©ã®ãã€ãã£ãåã«ã¯åé¡ããããŸããã次ã®ããã«ãšã¯ã¹ããŒãã§ããŸãã
sealed trait Repo { @JSExport def name: String
ã¯ã©ã¹ã®ã±ãŒã¹ãã£ãŒã«ãã¯ã
@(JSExport@field)
ã¢ãããŒã·ã§ã³
@(JSExport@field)
ã䜿çšããŠãšã¯ã¹ããŒãã§ããŸãã
forks
ããããã£ã®äŸïŒ
case class Origin(name: String, description: String, stargazersCount: Int, homepage: Option[String], @(JSExport@field) forks: Int) extends Repo
ãªãã·ã§ã³ããããããªãã¯ãããæšæž¬ããŸããã
homepage: Option[String]
åé¡ããããŸã
homepage: Option[String]
ã ãšã¯ã¹ããŒãããããšãã§ããŸããã
Option
ããå€ãååŸããããšã¯åœ¹ã«ç«ã¡ãŸãããjsã¯éçºè
ãäœããã®ã¡ãœãããåŒã³åºãå¿
èŠããããŸããã
Option
ã«ã€ããŠã¯äœããšã¯ã¹ããŒããããŠããŸããã
äžæ¹ãScalaã³ãŒããã·ã³ãã«ã§çŽæçãªãŸãŸã§ããããã«ã
Option
ãç¶æããããšæããŸãã ç°¡åãªè§£æ±ºçã¯ãç¹å¥ãªjs getterããšã¯ã¹ããŒãããããšã§ãã
import scala.scalajs.js.JSConverters._ sealed trait Repo {
è©ŠããŠã¿ãŸãããïŒ
console.log("fork.name: " + fork.name); console.log("fork.homepage: " + fork.homepage);
ç§ãã¡ã¯ãæ°ã«å
¥ãã®
Option
ãæ®ããŠãJSçšã®ãããã§çŸããAPIãäœããŸããã ãã£ãïŒ
äžèŠ§User.repos
ã¯
List
ã§ããããšã¯ã¹ããŒãã«åé¡ããããŸãã 解決çã¯åãã§ãJSé
åãšããŠãšã¯ã¹ããŒãããã ãã§ãïŒ
@JSExport("repos") def reposJS: js.Array[Repo] = repos.toJSArray
ãµãã¿ã€ãRepo
ç¹æ§ã«ã¯ãŸã 1ã€ã®åé¡ããããŸãã ã³ã³ã¹ãã©ã¯ã¿ãŒããšã¯ã¹ããŒãããªããããJSéçºè
ã¯ãã©ã®
Repo
ãµãã¿ã€ããæ±ã£ãŠããããææ¡ã§ããŸããã
Javascriptã«ã¯ãã¿ãŒã³ãããã³ã°ã¯ãªããç¶æ¿ã®äœ¿çšã¯ããã»ã©äžè¬çã§ã¯ãªãïŒãããŠè°è«ã®äœå°ãããïŒãããããã€ãã®ãªãã·ã§ã³ããããŸãã
hasForks: Boolean
ã¡ãœãããŸãã¯hasForks: Boolean
ã¡ãœãããäœæããŸãã ããã¯æ£åžžã§ãããååã«äžè¬åãããŠããŸãããtype: String
è¿œå type: String
ãã¹ãŠã®ãµãã¿ã€ãã®type: String
ããããã£ã
2ã€ã®æ¹æ³ãéžæããŸããæœè±¡åããŠãããžã§ã¯ãå
šäœã§ç°¡åã«äœ¿çšã§ããŸããtypeããããã£ããšã¯ã¹ããŒãããmixinã宣èšããŸãããã
trait Typed { self => @JSExport("type") def typ: String = self.getClass.getSimpleName } </code> , <code>type</code> Scala. <source lang="scala"> sealed trait Repo extends Typed {
...ãããŠããã䜿çšããŸãïŒ
å®æ°ãä¿åããŠããã°ãå°ãå®å
šã«ã§ããŸãïŒããã§ã³ã³ãã€ã©ã圹ç«ã¡ãŸãïŒã
class TypeNameConstant[T: ClassTag] { @JSExport("type") def typ: String = classTag[T].runtimeClass.getSimpleName }
ãã®ãã«ããŒã䜿çšããŠã
GitHub
ãªããžã§ã¯ãã§å¿
èŠãªå®æ°ã宣èšã§ããŸãã
@JSExportAll object Github {
ããã«ãããJavascriptã®è¡ãé¿ããããšãã§ããŸããäŸ
ãããããµãã¿ã€ãã®æäœæ¹æ³ã§ãã
ãšã¯ã¹ããŒããããªããžã§ã¯ããå€æŽã§ããªãå Žåã¯ã©ãããã°ããã§ããïŒãã®å Žåããããããã¯ãã¹ã³ã³ãã€ã«ãããã¢ãã«ã®ã¯ã©ã¹ãŸãã¯ã€ã³ããŒããããã©ã€ãã©ãªãããªããžã§ã¯ãããšã¯ã¹ããŒãããŠããŸãã ã¡ãœããã¯
Option
ãš
List
ã§åãã§ãããéãã1ã€ãããŸã-JSã®èŠ³ç¹ããåãå
¥ããããã©ãããŒã¯ã©ã¹ãšå€æãå®è£
ããå¿
èŠããããŸãã
ããã§ã¯ããšã¯ã¹ããŒãïŒ
Scala => JS
ïŒããã³ã€ã³ã¹ã¿ã³ã¹åïŒ
JS => Scala
ïŒã«ã®ã¿js眮æã䜿çšããããšãéèŠã§ãããã¹ãŠã®ããžãã¹ããžãã¯ã¯ãçŽç²ãªScalaã¯ã©ã¹ã«ãã£ãŠã®ã¿å®è£
ããå¿
èŠããããŸãã
Commit
ãšããã¯ã©ã¹ããã
Commit
ãŸããããã¯å€æŽã§ããŸããã
case class Commit(hash: String)
ãšã¯ã¹ããŒãæ¹æ³ã¯æ¬¡ã®ãšããã§ãã
object CommitJS { def fromCommit(c: Commit): CommitJS = CommitJS(c.hash) } case class CommitJS(@(JSExport@field) hash: String) { def toCommit: Commit = Commit(hash) }
次ã«ãããšãã°ã管çããã³ãŒãã®
Branch
ã¯ã©ã¹ã¯æ¬¡ã®ããã«ãªããŸãã
case class Branch(initial: Commit) { @JSExport("initial") def initialJS: CommitJS = CommitJS.fromCommit(initial) }
ã³ãããã¯JSç°å¢ã§ã¯
CommitJS
ãªããžã§ã¯ããšããŠè¡šãããããã
Branch
ã®ãã¡ã¯ããªã¡ãœããã¯æ¬¡ã®ããã«ãªããŸãã
@JSExport def createBranch(initial: CommitJS) = Branch(initial.toCommit)
ãã¡ãããããã¯åªããæ¹æ³ã§ã¯ãããŸããããã³ã³ãã€ã©ã«ãã£ãŠãã§ãã¯ãããŸãã ãã®ããããã®ãããªã©ã€ãã©ãªããå€ã¯ã©ã¹ã®ãããã·ãšããŠã ãã§ãªããäžå¿
èŠãªè©³çŽ°ãé ããŠAPIãç°¡çŽ åãããã¡ãµãŒããšããŠèŠãããšã奜ã¿ãŸãã
ã¢ã€ãã¯ã¹å®è£
ç°¡åã«ããããã«ããããã¯ãŒã¯ãªã¯ãšã¹ãã«ã¯
scalajs-domã©ã€ãã©ãªã®
Ajax
æ¡åŒµã䜿çšããŸãã ãšã¯ã¹ããŒããäžæããŠãAPIãå®è£
ããŠã¿ãŸãããã
ç©äºãè€éã«ããªãããã«ãAJAXã«é¢é£ãããã¹ãŠã®ãã®ã
API
ãªããžã§ã¯ãã«é
眮ããŸããããã«ã¯ããŠãŒã¶ãŒã®ããŒããšãªããžããªã®ããŒãã®2ã€ã®ã¡ãœããããããŸãã
APIãã¢ãã«ããåé¢ããDTOã¬ã€ã€ãŒãäœæããŸãã ã¡ãœããã®çµæã¯
Future[String \/ DTO]
ã«ãªãã
DTO
ã¯èŠæ±ãããããŒã¿ã®ã¿ã€ãã§ããã
String
ã¯ãšã©ãŒãè¡šããŸãã
object API { case class UserDTO(name: String, avatar_url: String) case class RepoDTO(name: String, description: String, stargazers_count: Int, homepage: Option[String], forks: Int, fork: Boolean) def user(login: String) (implicit ec: ExecutionContext): Future[String \/ UserDTO] = load(login, s"$BASE_URL/users/$login", jsonToUserDTO) def repos(login: String) (implicit ec: ExecutionContext): Future[String \/ List[RepoDTO]] = load(login, s"$BASE_URL/users/$login/repos", arrayToRepos) private def load[T](login: String, url: String, parser: js.Any => Option[T]) (implicit ec: ExecutionContext): Future[String \/ T] = if (login.isEmpty) Future.successful("Error: login can't be empty".left) else Ajax.get(url).map(xhr => if (xhr.status == 200) { parser(js.JSON.parse(xhr.responseText)) .map(_.right) .getOrElse("Request failed: can't deserialize result".left) } else { s"Request failed with response code ${xhr.status}".left } ) private val BASE_URL: String = "https://api.github.com" private def jsonToUserDTO(json: js.Any): Option[UserDTO] =
ã³ãŒãã®éã·ãªã¢ã«åã¯é衚瀺ã§ãããèå³æ·±ããã®ã§ã¯ãããŸãããã³ãŒãã200ã§ãªãå Žåã
load
ã¡ãœããã¯ãšã©ãŒæååãè¿ããŸããããã§ãªãå Žåãå¿çãJSONã«å€æããŠããDTOã«å€æããŸã
ããã§ãAPIã¬ã¹ãã³ã¹ãã¢ãã«ã«å€æã§ããŸãã
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue object Github {
ããã§ã¯ãmonadãã©ã³ã¹ãã©ãŒããŒã䜿çšããŠ
Future[\/[..]]
ãDTOãã¢ãã«ã«å€æããŸãã
ãã°ããããæ©èœçãªScalaã³ãŒãã®ããã«èŠããŸãã 次ã«ãã©ã€ãã©ãªã®ãŠãŒã¶ãŒåãã®
loadUser
ã¡ãœããã«
loadUser
ãŸãããã
æªæ¥ãå
±æããããã§è³ªåããããŸããJavascriptã§éåæåŒã³åºããåŠçããäžè¬çã«åãå
¥ããããŠããæ¹æ³ã¯äœã§ããïŒ jséçºè
ã¯ååšããªãã®ã§ãç¬ã£ãŠããŸãã ã³ãŒã«ããã¯ãã€ãã³ããšããã¿ãŒããããã¹ããã¡ã€ããŒããžã§ãã¬ãŒã¿ãŒãéåæ/åŸ
æ©ããã¹ãŠäœ¿çšãããŠããŸãããäœãéžæããå¿
èŠããããŸããïŒ Promisesã¯Scala Futureã«æãè¿ãå®è£
ã ãšæããŸãã çŽæã¯éåžžã«äººæ°ããã
ãå€ãã®ææ°ã®ãã©ãŠã¶ã§ããã«ãµããŒããããŠããŸãã æåã«ãçŽæã«ã€ããŠã³ãŒãã«äŒããå¿
èŠããããŸãã ããã¯ãTyped FacadeããšåŒã°ããŸãã ããã¯èªåã§ç°¡åã«è¡ãããšãã§ããŸãããscalajs-domã¯æ¢ã«å®è£
ãããŠããŸãã å®è£
ãèªåã§è¡ããã人ã®äŸã¯æ¬¡ã®ãšããã§ãã
trait Promise[+A] extends js.Object { @JSName("catch") def recover[B >: A]( onRejected: js.Function1[Any, B]): Promise[Any] = js.native @JSName("then") def andThen[B]( onFulfilled: js.Function1[A, B]): Promise[Any] = js.native @JSName("then") def andThen[B]( onFulfilled: js.Function1[A, B], onRejected: js.Function1[Any, B]): Promise[Any] = js.native }
ãŸãã
Promise.all
ãããªã¡ãœãããæã€ã³ã³ãããªã³ãªããžã§ã¯ãã ããã§ããã®ç¹æ§ãæ¡åŒµããã ãã§ãã
@JSName("Promise") class Promise[+R]( executor: js.Function2[js.Function1[R, Any], js.Function1[Any, Any], Any] ) extends org.scalajs.dom.raw.Promise[R]
ãããã£ãŠã
Future
ã
Promise
ã«å€æããã ãã§ãã æé»ã®ã¯ã©ã¹ã䜿çšããŠãããè¡ããŸãã
object promise { implicit class JSFutureOps[R: ClassTag, E: ClassTag](f: Future[\/[E, R]]) { def toPromise(recovery: Throwable => js.Any) (implicit ectx: ExecutionContext): Promise[R] = new Promise[R]((resolve: js.Function1[R, Unit], reject: js.Function1[js.Any, Unit]) => { f.onSuccess({ case \/-(f: R) => resolve(f) case -\/(e: E) => reject(e.asInstanceOf[js.Any]) }) f.onFailure { case e: Throwable => reject(recovery(e)) } }) } }
å埩æ©èœã¯ããèœã¡ãã
Future
ããèœã¡ãã
Promise
å€ããŸãã æ¡é
ã®å·ŠåŽãçŽæãç Žæ£ããŸãã
ããã§ã¯ãçŽæãå人ã®ããã³ããšã³ããšå
±æããŸãããããã€ãã®ããã«ãå
ã®ã¡ãœããã®é£ã®
Github
ãªããžã§ã¯ãã«è¿œå ããŸãã
def loadUser(login: String): Future[String \/ User] =
ããã§ããšã©ãŒãçºçããå ŽåãäŸå€ãããšã©ãŒã®ãããããã¹ãåé€ããŸãã ããã§ãAPIããã¹ãã§ããŸãã
ããã§ãFutureãšç§ãã¡ãæ
£ã芪ããã§ãããã¹ãŠã®ãã®ã䜿çšã§ããããã«ãªããŸãã-ããã§ããæ
£çšçãªJS APIãšããŠãšã¯ã¹ããŒãã§ããŸãã
çµè«Scala.jsã䜿çšããŠJavascriptã©ã€ãã©ãªãäœæããããã®ãã³ãã次ã«ç€ºããŸãã- èµ·åæã«ãšã¯ã¹ããŒãããããªããžã§ã¯ãããã£ãã·ã¥ããŸãã
- ã·ãŒã ã¬ã¹ã¿ã€ãããã®ãŸãŸãšã¯ã¹ããŒãããŸã ã
Option
ã List
ããã³ãã®ä»ã®ScalaããŒã¹ããšã¯ã¹ããŒãããªãã§ãã ããã js.UndefOr
ããã³js.Array
å€æããã²ãã¿ãŒã䜿çšããŸãã- ã³ã³ã¹ãã©ã¯ã¿ãŒããšã¯ã¹ããŒãããªãã§ãã ããã JSã«åªããå·¥å Žã䜿çšããŠãã ããã
- JSãã¬ã³ããªãŒãšã¯ã
js.*
ãåãå
¥ããããšãæå³ãjs.*
åãæšæºã®Scalaåã«å€æããŸãã - æååãã£ãŒã«ã
type
ãåèšã¿ã€ãã«ããã¯ã¹ããŸãã Future
ãJS Promise
ãšããŠãšã¯ã¹ããŒãããŸãã- Scalaã§æåã«æžããŠãã ããã Scalaéçºè
ãšããŠã®èªå·±è¡šçŸã«éå®ãããèšèªæ©èœãæ倧éã«æŽ»çšããŠãã ããã
ããã§ãããããã¹ãŠããšã¯ã¹ããŒãã§ããããšãããããŸããã
ãµã³ãã«ã³ãŒãã¯GitHubã«ãããŸãïŒ
https :
//github.com/vpavkin/scalajs-library-tips
ãŠã©ãžããŒã«ã»ãããã³ã¹ã«ã©éçºè