🔧

高階関数でコード重複を半分以下に!Scala 2 REPLハンズオン講座

に公開

— 実行しながら 重複除去テクニック を体感する

0. REPL を開く

scala

1. “コピペ量産”版 V1

import java.io.File
object FileMatcherV1 {
  private val filesHere = new File(".").listFiles

  def filesEnding(q: String)      = for (f <- filesHere; if f.getName.endsWith(q))  yield f
  def filesContaining(q: String)  = for (f <- filesHere; if f.getName.contains(q))  yield f
  def filesRegex(q: String)       = for (f <- filesHere; if f.getName.matches(q))   yield f
}

実行例

scala> FileMatcherV1.filesEnding(".scala").map(_.getName).toList
val res0: List[String] = List(b.scala, scala.scala, c.scala, a.scala)

scala> FileMatcherV1.filesContaining("c").map(_.getName).toList
val res1: List[String] = List(b.scala, scala.scala, c.scala, a.scala)

2. 高階関数で共通化 V2

import java.io.File
object FileMatcherV2 {
  private val filesHere = new File(".").listFiles

  private def filesMatching(q: String,
                            matcher: (String, String) => Boolean) =
    for (f <- filesHere; if matcher(f.getName, q)) yield f

  def filesEnding(q: String)      = filesMatching(q, _.endsWith(_))
  def filesContaining(q: String)  = filesMatching(q, _.contains(_))
  def filesRegex(q: String)       = filesMatching(q, _.matches(_))
}

実行例

scala> FileMatcherV2.filesRegex("a\\.scala").map(_.getName)
val res2: Array[String] = Array(a.scala)

付録 — カリー化 & クロージャでもっと短く

Scala らしい書き方として参考までに

import java.io.File
object FileMatcherCurried {
  private val filesHere = new File(".").listFiles

  private def filesMatching(q: String)           // ← 第1引数リスト
                       (matcher: String => Boolean) = // ← 第2引数リスト
    for (f <- filesHere; if matcher(f.getName)) yield f

  def filesEnding(q: String)      = filesMatching(q)(_.endsWith(q))
  def filesContaining(q: String)  = filesMatching(q)(_.contains(q))
  def filesRegex(q: String)       = filesMatching(q)(_.matches(q))
}

実行例

scala> FileMatcherCurried.filesContaining("b").map(_.getName)
val res3: Array[String] = Array(b.scala)

🔹 カリー化(Currying)とは?

* 複数の引数リストに分けるテクニック

def f(a)(b) = ... と書くと
f(a) の時点で (b) => … な 部分適用関数が返る。

🔹 クロージャとは?

* 関数値が スコープ外の変数を捕捉(持ち歩き)できる性質。

ここでは q を閉じ込めた _.endsWith(q) などがクロージャ。

おわりに ☕️

1.	似た処理はまず高階関数 (V2)
2.	さらに カリー化+クロージャで API 化 (付録A)
3.	行数 33 → 14、重複 for は 3→1 のスッキリ感を体験しよう!

Scala REPL だけで動かせるので、ぜひ手を動かしてみてください。
Happy Scala Hacking! 🐾

参考文献

  • マーティン・オーダースキー, ほか.
    『Scala スケーラブルプログラミング[第4版]』
    第9章「制御構造の抽象化」9.1節 コードの重複の削減.
    オライリー・ジャパン, 2021.

Discussion