Help us understand the problem. What is going on with this article?

Scala.jsでオブジェクトを渡すときのFacadeの書き方

More than 1 year has passed since last update.

Scala.jsで関数の引数にオブジェクトを渡すときの書き方について考える。
js.Objectを用いて定義せずに詳細に定義することを考える。

結論としてはこんな感じ

  • 引数には Non-native JS trait を用いる
  • プロパティのありなしを区別したいなら区別する分だけtraitを作る
  • applyメソッドを用意しておくと便利
  • 構造的部分型は使えない。(使う方法が思いつかない)
  • nullは現状扱いづらいので極力使いたくない。別の仕組みが必要
    • Optionはundefinedとマッピングされている

FlowTypeの型定義を対比させつつ書く(参考 https://qiita.com/eielh/items/a6c162a60305d7668fd2)

前準備

登場するFacadeはすべてconsole.logにわりあててます

hoge.js
module.exports = {
  hoge: console.log,
  hoge2: console.log,
  hoge3: console.log,
};

Facade

@JSImport("hoge.js", JSImport.Namespace)
@js.native
object Hoge extends js.Any {

  /* aの値はundefinedやnullになってもよいが必須 */
  /* function hoge(x: {| a: ?number |}) */
  def hoge(hoge: HogeArgs): Unit = js.native

  /* a はあってもなくてもよい。nullはだめ */ // null はだめは何らかの仕組みを考えないと制限できない。
  /* function hoge2(x: {| a?: number |}) */
  def hoge2(hoge: Hoge2Args): Unit = js.native

  /* a は必須 */
  /* function hoge3(x: {| a: number |}) */
  def hoge3(hoge: Hoge3Args): Unit = js.native
}

function hoge(x: {| a: ?number |})

aというプロパティは必要だけど値はnullundefinedであっても構わない。

利用例

object Hoge1 {
  import Hoge._

  def sample(): Unit = {
    println("hoge1")
    hoge(HogeArgs(a = 1))  // => { a: 1 }
    hoge(HogeArgs(a = js.undefined))  // => { a: undefiend }
    hoge(HogeArgs(a = null))  // => { a: null } // しかし nullはかきたくない。
    hoge(HogeArgs()) // => {a: undefiend }
  }
}

Scala上のコードでnullは使いたくないが、nullにマッピングされているのはnullのみ。
代わりの値を用意したほうが良さそうだけど、今回は無視。

引数用のtrait用意

trait HogeArgs extends js.Object {
  val a: js.UndefOr[Int] = js.undefined
}

applyの用意

object HogeArgs {
  def apply(a: js.UndefOr[Int] = js.undefined): HogeArgs = {
    val a_ = a
    new HogeArgs {
      override val a: js.UndefOr[Int] = a_
    }
  }
}

a_に束縛しているのは名前がかぶってしまうためです。
他によい回避方法があったら教えてください。
デフォルト引数でaの指定がなければundefinedになるようになっています。

function hoge2(x: {| a?: number |})

aはあってもなくてもよいが、値はnullになってはだめ。undefinedはないのと同じ扱い。

しかし、Scalaの都合上、nullは渡せてしまう。
ので、これを使う意味はあまりない。

利用例

object Hoge2 {
  import Hoge._

  def sample(): Unit = {
    println("hoge2")
    hoge2(Hoge2Args(a = 1)) // => { a: 1 }
    // hoge2(Hoge2Args(a = js.undefined)) // compile error
    // hoge2(Hoge2Args(a = null)) // compile error
    hoge2(Hoge2Args()) // => {}  // { a: undefined } とは等価と考える
  }
}

引数用のtrait用意

trait Hoge2Args extends js.Object

trait Hoge2ArgWithA extends Hoge2Args {
  val a: Int
}

引数を区別するには複数traitを作らざるを得ない。

あってもなくても良い値が増えてくると組み合わせが爆発するので、js.UnedfOr[Int]にしたほうが楽かもしれない。その場合、nullが渡せてしまう。

// 別解
trait Hoge2Args extends js.Object

trait Hoge2ArgWithA extends Hoge2Args {
  val a: Int
}

object Hoge2 {
  import Hoge._

  def sample(): Unit = {
    println("hoge2")
    hoge2(Hoge2Args(a = 1)) // => { a: 1 }
    hoge2(Hoge2Args(a = js.undefined)) // => { a: undefined }
    hoge2(Hoge2Args(a = null)) // => { a: null } // compile errorにしたい
    hoge2(Hoge2Args()) // => {}  // hogeとは結果がちがう
  }
}

applyの用意

object Hoge2Args {
  def apply(a: js.UndefOr[Int]): Hoge2Args = {
    val a_ = a
    new Hoge2ArgWithA {
      override val a: js.UndefOr[Int] = a_
    }
  }

  def apply(): Hoge2Args = {
    new Hoge2Args {}
  }
}

オーバーロードしてしまえばよい。

function hoge3(x: {| a: number |})

aは必須。

利用例

object Hoge3 {
  import Hoge._

  def sample(): Unit = {
    println("hoge3")
    hoge3(Hoge3Args(a = 1)) // => { a: 1 }
    // hoge3(Hoge3Args(a = js.undefined)) // compile error
    // hoge3(Hoge3Args(a = null)) // compile error
    // hoge3(Hoge3Args()) // compile error
  }
}

引数用のtrait用意

trait Hoge3Args extends js.Object {
  val a: Int
}

applyの用意

object Hoge3Args {
  def apply(a: Int): Hoge3Args = {
    val a_ = a
    new Hoge3Args {
      override val a: Int = a_
    }
  }
}

特にコメントはない。

まとめ

頭にかいた通りなので、特にない。
Non-native JS traitを使うのは型安全するためであるが、詳細は関連記事など参照ください。

関連

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした