Kotlinでカリー化


JSさんと戯れていたらカリー化が分かった気がするのでKotlinでも書いてみます。

## 元のコード

日報の中などで使われる、勤務時間を表す時刻データをいい感じにフォーマットする処理について考えます。

* 開始時刻
* 終了時刻
* 休憩時間

上記のデータで、勤務時間と休憩時間を表現します。

```scala:BeforeCurrying.kt
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date

fun main(args: Array<String>) {
    val startTime = Time(9,0)
    val endTime = Time(18,15)
    val breakTime = Time(1,15)

    val startTimeStr = formatTime("HH:mm", startTime)
    val endTimeStr = formatTime("HH:mm", endTime)
    val breakTimeStr = formatTime("HH時間mm分", breakTime)
    
    println("勤務時間: $startTimeStr 〜 $endTimeStr")
    println("休憩時間: $breakTimeStr")
    // 勤務時間: 09:00 〜 18:15
    // 休憩時間: 01時間15分
}

fun formatTime(format: String, time: Time): String {
    val formatter = SimpleDateFormat(format)
    return formatter.format(timeToDate(time))
}

fun timeToDate(time: Time): Date {
    val cal = Calendar.getInstance()
    cal.set(Calendar.HOUR_OF_DAY, time.hour)
    cal.set(Calendar.MINUTE, time.min)
    return cal.getTime()
}

data class Time(val hour:Int, val min:Int)
```

このコードで憎しみを感じるのは次のあたりです。

```scala:kotlin
val startTimeStr = formatTime("HH:mm", startTime)
val endTimeStr = formatTime("HH:mm", endTime)
val breakTimeStr = formatTime("HH時間mm分", breakTime)
```

`HH:mm`と`HH時間mm分`という二種類のフォーマットがあるので、フォーマット文字列をパラメータとして持つ関数を定義するのはよいことです。また、フォーマット対象の時刻データも入れないといけないので、引数がふたつになるのも順当でしょう。

ただ、startTimeとendTimeに向けて、`HH:mm`を二度も記述しているのはいただけません。同じフォーマットであることが分かっているのであれば、共通で扱えるようにするべきです。

`HH:mm`を文字列定数として抜き出せばよいでしょうか？　それはそれでよいですが、今回はカリー化しましょう。

## カリー化後のコード

というわけでカリー化してみました。

```scala:Curried.kt
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date

fun main(args: Array<String>) {
    val startTime = Time(9,0)
    val endTime = Time(18,15)
    val breakTime = Time(1,15)

    val formatColonTime = timeFormatter("HH:mm")
    val formatSpanTime = timeFormatter("HH時間mm分")
    
    val startTimeStr = formatColonTime(startTime)
    val endTimeStr = formatColonTime(endTime)
    val breakTimeStr = formatSpanTime(breakTime)
    
    println("勤務時間: $startTimeStr 〜 $endTimeStr")
    println("休憩時間: $breakTimeStr")
    // 勤務時間: 09:00 〜 18:15
    // 休憩時間: 01時間15分
}

fun timeFormatter(format: String): (Time) -> String {
    val formatter = SimpleDateFormat(format)
    return {time -> formatter.format(timeToDate(time))}    
}

fun timeToDate(time: Time): Date {
    val cal = Calendar.getInstance()
    cal.set(Calendar.HOUR_OF_DAY, time.hour)
    cal.set(Calendar.MINUTE, time.min)
    return cal.getTime()
}

data class Time(val hour:Int, val min:Int)
```

新設したtimeFormatter関数は、カリー化前のformatTimeに似ていますが、引数と戻り値が違います。

```scala:カリー化前
fun formatTime(format: String, time: Time): String {
    val formatter = SimpleDateFormat(format)
    return formatter.format(timeToDate(time))
}
```

```scala:カリー化後
fun timeFormatter(format: String): (Time) -> String {
    val formatter = SimpleDateFormat(format)
    return {time -> formatter.format(timeToDate(time))} // クロージャを返すようになった
}
```

これにより、用途に応じてフォーマット文字列を適用済みの関数を生み出すことができるようになりました。

```scala:kotlin
val formatColonTime = timeFormatter("HH:mm") // コロン区切りの表現用
val formatSpanTime = timeFormatter("HH時間mm分") // 時間の表現用

val startTimeStr = formatColonTime(startTime)
val endTimeStr = formatColonTime(endTime)
val breakTimeStr = formatSpanTime(breakTime)
```

この時点でも割と嬉しいですが、フォーマットの種類が多くなってきたりすると、もっと嬉しそうですね。

## まとめ

Kotlinのリハビリを兼ねて書いてみました。

複数のパラメータを時間差で適用したいときにはカリー化すごい便利ですね。状態を持たないのも魅力的です。

## 宣伝

Kotlinを書くお仕事はしばらくなさそうですが、ウォーターセル株式会社では農業ITに興味のあるエンジニアを募集しています！

* [農業支援システム「アグリノート」のエンジニア ＆ デザイナー大募集！！ - ウォーターセル株式会社の求人 - Wantedly](https://www.wantedly.com/projects/5103)