Scala.js:

Когда TypeScript'a уже мало

Бадальянц Юрий, 2017

О себе

  • Программирую с 2011 года
  • PHP
  • JavaScript
  • Java
  • Scala

TypeScript

≈ JavaScript + Статическая типизация

* и Flow

Динамическая типизация в JS

+ Быстрый старт (низкий порог входа)

+ Код пишется быстро

+ Удобно для прототипов и маленьких проектов

- Сложно поддерживать в большом проекте

- Все ошибки только в рантайме

- Сложно рефакторить и улучшать код

- Легко допустить ошибку

TypeScript

  • Добавляется компилятор с проверкой типов
  • Код легче поддерживать
  • Типы - документация
  • Легкие рефакторинги
  • Чем больше проект - тем больше профит

Но действительно ли TypeScript избавляет от всех проблем?

Scala

  • Богатые ООП и ФП возможности
  • Все ES5 фичи
  • Все ES6 фичи
  • Все ES7 фичи
  • Почти все TypeScript фичи
  • И многое другое
  • Номинальная статическая типизация
  • Scala.js - Scala, которая компилируется в JavaScript

var xhr = new XMLHttpRequest()

xhr.open("GET",

  "https://api.twitter.com/1.1/search/" +

  "tweets.json?q=%23scalajs"

)

xhr.onload = (e) => {

  if (xhr.status == 200) {

    var r = JSON.parse(xhr.responseText)

    $("#tweets").html(parseTweets(r))

  }

}

xhr.send()

JavaScript

Scala.js

var xhr = new XMLHttpRequest()

xhr.open("GET",

  "https://api.twitter.com/1.1/search/" +

  "tweets.json?q=%23scalajs"

)

xhr.onload = (e: Event) => {

  if (xhr.status == 200) {

    var r = JSON.parse(xhr.responseText)

    $("#tweets").html(parseTweets(r))

  }

}

xhr.send()

JavaScript

Scala.js

var xhr = new XMLHttpRequest()

xhr.open("GET",

  "https://api.twitter.com/1.1/search/" +

  "tweets.json?q=%23scalajs"

)

xhr.onload = (e: Event) => {

  if (xhr.status == 200) {

    var r = JSON.parse(xhr.responseText)

    $("#tweets").html(parseTweets(r))

  }

}

xhr.send()

var xhr = new XMLHttpRequest()

xhr.open("GET",

  "https://api.twitter.com/1.1/search/" +

  "tweets.json?q=%23scalajs"

)

xhr.onload = (e) => {

  if (xhr.status == 200) {

    var r = JSON.parse(xhr.responseText)

    $("#tweets").html(parseTweets(r))

  }

}

xhr.send()

function updateUserPassword(
    userId: String, 
    password: String
): Promise<void> {
    // implementation
}
updateUserPassword(password, userId)
function updatePasswordForUser(
    password: String, 
    userId: String
): Promise<void> {
    // implementation
}
const userId = "16c8bde9-7c3a-400d-aead-bfec582b7103"
const password = "1q2w3e4r5t"
updatePasswordForUser(password, userId)

Ошибка при

рефакторинге

class UserId {
    value: String
    constructor(str: String) {
        this.value = str
    }
}
class Password {
    value: String
    constructor(str: String) {
        this.value = str
    }
}
const userId = new UserId("16c8bde9-7c3a-400d-aead-bfec582b7103")
const password = new Password("1q2w3e4r5t")
function updatePasswordForUser(
    password: Password, 
    userId: UserId
): Promise<void> {
    // implementation
}
function updateUserPassword(
    userId: UserId, 
    password: Password
): Promise<void> {
    // implementation
}
updateUserPassword(password, userId)
updateUserPassword(password, userId) // OK!

Structural typing

class UserId {
    userId: String
    constructor(str: String) {
        this.userId = str
    }
}
class Password {
    password: String
    constructor(str: String) {
        this.password = str
    }
}

Костыль

Boilerplate

Runtime

overhead

updateUserPassword(password, userId)
updateUserPassword(password, userId) // ERROR
class UserId {
    value: String
    constructor(str: String) {
        this.value = str
    }
}
class Password {
    value: String
    constructor(str: String) {
        this.value = str
    }
}
case class UserId(value: String) extends AnyVal
case class Password(value: String) extends AnyVal
val userId = UserId("16c8bde9-7c3a-400d-aead-bfec582b7103")
val password = Password("1q2w3e4r5t")
def updatePasswordForUser(
  password: Password, 
  userId: UserId
): Future[Unit] = {
  // implementation
}
def updateUserPassword(
  userId: UserId, 
  password: Password
): Future[Unit] = {
  // implementation
}
updateUserPassword(password, userId)
updateUserPassword(password, userId) // ERROR
const libPromise: Promise<void> = someLibFunc()
function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
    const timeoutPromise = new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error("Promise timed out")), timeout)
    })
    return Promise.race([promise, timeoutPromise])
}
withTimeout(libPromise, 1000)
  .then((value) => {
    console.log(value)
  })
  .catch(e => {
    console.error(e)
  })
libPromise.withTimeout(1000)
  .then((value) => {
    console.log(value)
  })
  .catch(e => {
    console.error(e)
  })

Prototype

monkey

patching

val libFuture: Future[Unit] = someLibFunc()
implicit class FutureExtensions[T](val self: Future[T]) extends AnyVal {
  def withTimeout(timeout: Long): Future[T] = {
    val promise = Promise[T]()
    setTimeout(() => {
      promise.failure(new RuntimeException("Future timed out"))
    }, timeout)
    Future.firstCompletedOf(Seq(self, promise.future))
  }
}
libFuture.withTimeout(1000).onComplete {
  case Success(value) => println(value)
  case Failure(e) => println(e)
}
val timeout = 3000
getUser(someUserId, timeout)
updateUser(someUser, timeout)
deleteUser(someUserId, timeout)
val timeout = 3000
getUser(someUserId, timeout)
updateUser(someUser, timeout)
deleteUser(someUserId, timeout)
def getUser(userId: UserId, timeout: Long): Future[Option[User]]
def updateUser(user: User, timeout: Long): Future[Unit]
def deleteUser(userId: UserId, timeout: Long): Future[Unit]
def getUser(userId: UserId)(implicit timeout: Long): Future[Option[User]]
def updateUser(user: User)(implicit timeout: Long): Future[Unit]
def deleteUser(userId: UserId)(implicit timeout: Long): Future[Unit]
implicit val timeout: Long = 3000
getUser(someUserId)
updateUser(someUser)
deleteUser(someUserId)
implicit val timeout = 3000
getUser(someUserId)
updateUser(someUser)
deleteUser(someUserId)
libFuture.withTimeout(1000).onComplete {
  case Success(value) => println(value)
  case Failure(e) => println(e)
}
val sessionTime = 3600
val activeTime = 60

Неочевидно

implicit val timeout = 3000 millis
getUser(someUserId)
updateUser(someUser)
deleteUser(someUserId)
libFuture.withTimeout(1 second).onComplete {
  case Success(value) => println(value)
  case Failure(e) => println(e)
}
val sessionTime = 1 hour
val activeTime = 60 seconds
def seconds = ???
obj.seconds
obj seconds // postfix notation
1.second // implicit class + normal notation
1 second // implicit class + postfix notation

DSL

html(
  head(
    script(src:="..."),
    script(
      "alert('Hello World')"
    )
  ),
  body(
    div(
      h1(id:="title", "This is a title"),
      p("This is a big paragraph of text")
    )
  )
)

DSL

'li("class" -> itemState,
  'div("class" -> "view",
    'input("class" -> "toggle",
      "type" -> "checkbox",
      "checked" -> props.item.checked,
      "onclick" -> toggle _
    ),
    'label("ondblclick" -> editStart _, props.item.title),
    'button("class" -> "destroy",
      "onclick" -> destroy _
    )
  )
)

DSL

<footer class="info">
  <p>Double-click to edit a todo</p>
  <p>Written by <a href="https://github.com/atry">Yang Bo</a></p>
  <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>

DSL

<table border="1" cellPadding="5">
  <tbody>
    {
      for (contact <- data) yield {
        <tr>
          <td>
            {contact.name.bind}
          </td>
          <td>
            {contact.email.bind}
          </td>
        </tr>
      }
    }
  </tbody>
</table>

Полезные ссылки

http://www.scala-js.org/ - официальный сайт scala.js

https://gitter.im/scala-js/scala-js - gitter канал scala.js

http://www.lihaoyi.com/hands-on-scala-js/ - электронная книга

https://telegram.me/scala_ru - русскоязычный telegram канал

Спасибо!

@lmnet89

lmnet

lmnet89@gmail.com

Бадальянц Юрий, 2017

www.linkedin.com/in/lmnet

Scala.js:Когда TypeScript'a уже мало

By Yury Badalyants

Scala.js:Когда TypeScript'a уже мало

  • 561