Scalaでcase classを展開して引数として渡す方法

# TL;DR

## やりたいこと

関数に対してcase classをそのまま渡して実行したい。

```scala
scala > def printProfile(name: String, age: Int): Unit = println(s"${age}歳の${name}です")
scala > case class Profile(name: String, age: Int)
scala > val p = Profile("山田", 25)

scala > printProfile(p.name, p.age) // いちいち引数を書くのが面倒くさい！
25歳の山田です
scala > printProfile.よしなにしてくれるメソッド(p) // case classインスタンスpを渡すだけで実行してほしい!
25歳の山田です
```

## できるようにしました → [bigwheel/case\-class\-expand\-and\-apply](https://github.com/bigwheel/case-class-expand-and-apply)

[ライブラリを実装して](https://github.com/bigwheel/case-class-expand-and-apply)次のように実行できるようにしました[^3]。

[^3]: expandAndApplyというメソッド名がダサいとかメソッドを記号化したほうが( `<<|` とかね)使いやすくない？とかあるんですがいい名前が浮かばなかったのとscalaオフィシャルでもメソッドの記号表記は今逆風っぽいので記号化は一旦していません。真っ当な理由があったらメソッドのリネーム、エイリアス定義は前向きに検討しますのでプルリクください

```scala
scala > (printProfile _).expandAndApply(p)
25歳の山田です
```



ライブラリの利用方法はいつもの感じです。READMEを参照してください。


[Scala 2.13で追加されるpipeメソッド](https://qiita.com/bigwheel/items/03dbca24d88c9256704c)に合わせたもの(expandAndApplyのレシーバと引数を入れ替えたもの)も使えます。

```scala
scala > p.expandAndPipe(printProfile _)
25歳の山田です
```

# 背景詳細

```scala
scala > def printProfile(name: String, age: Int): Unit = println(s"${age}歳の${name}です")
```

こういうメソッドがあるとします。

<!-- ## 1. 通常の呼び出し -->

普通に呼び出すときは

```scala
scala> printProfile("山田", 25)
25歳の山田です
```

これで良いですが、この引数を1セットで引き回したいときがあります。

<!-- ## 2. Tupleの導入 -->

そんなときはTupleを使う手があります。

```scala
scala> val profile = ("山田", 25)
profile: (String, Int) = (山田,25)

scala> printProfile(profile._1, profile._2)
25歳の山田です
```

<!-- ## 3. Tupleの引数展開の自動化 -->

手で\_1, \_2と展開するのが面倒になったときは以下の書き方もあります([参考元](https://stackoverflow.com/a/3568076/4006322))[^1]。

[^1]: ちなみに`printProfile`をFunction2型として扱うための`xxx _`はScalaの言語仕様上避けられないようです。正直ここはちょっとイケてないなと思います。トレードオフになりますが、例えば関数実行には必ず`()`をつける、という文法にしてしまえば`xxx _`は要らなくなるでしょう

```scala
scala> (printProfile _).tupled(profile)
25歳の山田です
```

<!-- ## 4. Tupleの問題 -->

ただ、Tupleは中身の型とその順番でのみ一意性が定義されます。
言い換えると、中身の値の型とその順番が一緒だとまったく意図していないインスタンスでも以下のように実行できてしまいます。

```scala
scala> val item = ("ファンタ", 120)
scala> (printProfile _).tupled(item)
120歳のファンタです
```

<!-- ## 5. case classの導入 -->

そこでcase classが導入されるのは自然な流れです[^2]。

[^2]: 実際のところ、scalaではcase classが簡単に定義できること、上記のTupleの型的な危険性から動的型付け言語ではTupleが使われるシーンのほとんどでcase classが使われます。Tupleが使われるときはcase classの定義すら省略したいほど局所的に値の組み合わせを一緒に引き回したいときで、具体的には無名関数を定義するとき、複数の値をメソッドの返り値として返したいときなどがあります

```scala
scala> case class Profile(name: String, age: Int)
scala> val ccProfile = Profile("山田", 25)
```

<!-- ## 6. case classの手書きでの展開 -->

ただしcase classはTupleではないのでそのままだとまた手書きで展開する必要があります。

```scala
scala> printProfile(ccProfile.name, ccProfile.age)
25歳の山田です
```

<!--

// メソッドオーバーロードについてはcase classに限定せずtupleでも同じ手法が使えるのでconfusingと判断して一旦省略

メソッドオーバーロードを利用してcase class用のエイリアスを定義するのはいいアイデアです。

```scala
scala> def printProfile(p: Profile): Unit = printProfile(p.name, p.age)
scala> printProfile(ccProfile)
25歳の山田です
```

対象のメソッドが外部ライブラリ内でクラス自身に直接メソッドを追加できないときや、レイヤードアーキテクチャなどの思想により下層のクラスへ上層のcase classを持ち込みたくない場合でも、implicit classのテクニックを使えば動的にメソッドを生やすことができます。


そう、Scalaならね。
-->

Tupleで一度解決した問題がまた発生しました。

<!-- ## 7. case classの引数展開の自動化 ver.1 -->

case classはCompanion Objectにunapplyというcase classインスタンスを対応するTupleへ変換するメソッドが必ず定義されます。それを利用すれば以下のようにできます。

```scala
scala> (printProfile _).tupled(Profile.unapply(ccProfile).get)
25歳の山田です
```

<!-- ## 8. case classの引数展開の自動化 ver. 2 -->

ですが `Profile.unapply(ccProfile).get` のところにぎこちなさを感じます。
確実に変換できることが保証されているにもかかわらず `unapply` の仕様上 `Option` 型でくるんで返されるためgetメソッドを必ず書いてやる必要があります。またコンパニオンオブジェクトである `Profile` のメソッドを呼んでいることも微妙な気がします。`ccProfile.toTuple` みたいなインスタンスメソッドがほしいですね。

あります。[shapeless](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.1.0)ライブラリを使いましょう。

```scala
scala> import shapeless.syntax.std.product._
scala> (printProfile _).tupled(ccProfile.toTuple)
25歳の山田です
```

これでかなりマシになりました。
ただ、論理的には「関数へcase classインスタンスを渡す」というように表現したいにもかかわらずコード中でTupleを中継していることが気になります。
implicit classを使いFunction2にメソッドを追加してTupleを隠蔽しましょう。

```scala
scala> :paste
// Entering paste mode (ctrl-D to finish)

import shapeless.ops.product.ToTuple
implicit class RichFunction2[T1, T2, R](f: (T1, T2) => R) {
  type ARGUMENTS = (T1, T2)
  def expandAndApply[CASECLAZZ <: Product](t: CASECLAZZ)
    (implicit toTuple: ToTuple.Aux[CASECLAZZ, ARGUMENTS]): R = {
    f.tupled(t.toTuple[ARGUMENTS])
  }
}

// Exiting paste mode, now interpreting.

scala> (printProfile _).expandAndApply(ccProfile)
25歳の山田です
```

やりました！ scalaの言語仕様ギリギリまでシンプルに表現できたと思います。

ただしこのRichFunctionNは1から22のすべてのFunctionNトレイトに対して定義してやる必要があるのでかなり手間です。
そこでライブラリ化した、というのが今回の背景になります。

# 残タスク

今回やりたかったことはほぼほぼ達成したのですが、今回のコードは結局関数とcase classを引き合わせるために合流点でTupleを使っている関係上実はcase classの型安全性は損なわれています。
どういうことかというとつまり変数の型とその順番さえ一致していればどのようなcase classでもexpandAndApplyを呼び出せるようになっているということです。Tupleの項目で言及してものと同じ問題が発生しているんですね。

間違ったcase classを渡す危険性を少しでも下げるために、例えば各引数とcase classのメンバーの変数名が一致するかをマクロでチェックしてもいいかもしれません。
偶然変数の型と順番が一致することはあっても、変数名が一致することはそうそうないだろうという前提に基づいた工夫ですね。

ただまああくまで小手先に過ぎない上、変数名を厳密に一致させる必要があるというデメリットも生まれてしまうため微妙な気もします。やるとしても `expandAndApply` とは別の名前にしたほうが良さそうですね( `expandAndApplyStrictly` など)。

# 作成動機

そもそもなぜこんなライブラリを作ったかというと、Pythonのようにcase classの各要素を引数リストへunpackしたいと思ったことがきっかけです。
実用性や筋の良し悪しはともかく、作成中はマクロを勉強したりScala本体のFunctionX, Productなどのコードを読んだりで非常に楽しかったので良い経験になりました。

# 注意書き(あるいは免責)

このやりたいことに対して手段が適当でない気もずっとしていますが、こういった方法もあるよということで作ってみました。
Scalaのそこそこの歴史でもこういったことが具体的にしたい、または実現したというような話がないのでみんな必要性を感じていないか、そもそも解決法が間違っているのではないかと思います。
そもそも筋が悪い、こっちがScala way的なツッコミあればコメントぜひお願いします(丸投げ)。

# 参考元

* [parameters \- scala tuple unpacking \- Stack Overflow](https://stackoverflow.com/questions/3568002/scala-tuple-unpacking/3568076#3568076)
* [Feature overview: shapeless 2\.1\.0 · milessabin/shapeless Wiki](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.1.0)
* [Unpack a list in Python? \- Stack Overflow](https://stackoverflow.com/questions/3480184/unpack-a-list-in-python)