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

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

```Scala
@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`というプロパティは必要だけど値は`null`や`undefined`であっても構わない。

### 利用例

```scala
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用意

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

### applyの用意

```scala

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`は渡せてしまう。
ので、これを使う意味はあまりない。


### 利用例

```scala
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用意

```scala
trait Hoge2Args extends js.Object

trait Hoge2ArgWithA extends Hoge2Args {
  val a: Int
}
```

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

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

```scala
// 別解
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の用意

```scala

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`は必須。

### 利用例

```scala
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用意

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

### applyの用意

```scala

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

特にコメントはない。

# まとめ

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


# 関連

* [リポジトリ](https://github.com/eiel-sample-code/use-object-args-in-scala.js-facade)
* [Scala.js で CommonJSモジュールを使う](https://qiita.com/eielh/items/5a78a74292d4c458c1a5)
* [Scala traitとnative JS traitとnon-naitive JS trait](https://qiita.com/eielh/items/40d0c1ba9cec45bb2a34)
* [JavaScript types - Scala.js](https://www.scala-js.org/doc/interoperability/types.html)
* [Write facade types for JavaScript APIs - Scala.js](https://www.scala-js.org/doc/interoperability/facade-types.html)
* [Non-native JS types (aka Scala.js-defined JS types) - Scala.js](https://www.scala-js.org/doc/interoperability/sjs-defined-js-classes.html)