Tour of Scala

高階関数

Language

高階関数は他の関数をパラメーターとして受け取る、もしくは結果として関数を返します。 このようなことができるのは、Scalaでは関数が第一級値 (first-class value) だからです。 用語が少し紛らわしいかもしれませんが、 ここでは”高階関数”というフレーズを関数をパラメーターとして受け取る、または関数を返すメソッドと関数の両方に対して使います。

もっとも一般的な例の1つは、Scalaのコレクションで利用可能な高階関数mapです。

val salaries = Seq(20000, 70000, 40000)
val doubleSalary = (x: Int) => x * 2
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)

doubleSalaryはIntxを1つだけ受け取り、x * 2を返す関数です。 一般的に、アロー=>の左側のタプルは引数リストであり、右側の式の値が返されます。 3行目で、給与のリストのそれぞれの値にdoubleSalaryが適用されます。

コードを減らすため、以下のように無名関数を作ることができ、引数として直接mapに渡すことができます

val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)

上記例ではxをIntとして宣言していないことに注意してください。 それはmap関数が期待する型を基にコンパイラーが型を推論できるからです。 さらに言えば、慣用的には同じコードを以下のように書きます。

val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(_ * 2)

Scalaコンパイラはパラメーターの型を(Intが1つだけと)既に知っているため、関数の右側を提供するだけでよいです。 唯一の注意点はパラメータ名の代わりに_を使う必要があるということです(先の例ではxでした)。

メソッドを関数に強制変換

高階関数には引数としてとしてメソッドを渡すことも可能で、それはScalaコンパイラがメソッドを関数に強制変換するからです。

case class WeeklyWeatherForecast(temperatures: Seq[Double]) {

  private def convertCtoF(temp: Double) = temp * 1.8 + 32

  def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- convertCtoFメソッドが渡されます
}

ここで、メソッドconvertCtoFforecastInFahrenheitに渡されています。 これはコンパイラがconvertCtoFを関数x => convertCtoF(x)(注意点:xはスコープ内でユニークであることが保証された名前になります)に変換することで実現します。

関数を受け取る関数

高階関数を使う理由の1つは余分なコードを削減することです。 たとえば、何通りかの係数で人の給料を上げるメソッドが欲しいとしましょう。 高階関数を作らないなら、こんな感じになるかもしれません。

object SalaryRaiser {

  def smallPromotion(salaries: List[Double]): List[Double] =
    salaries.map(salary => salary * 1.1)

  def greatPromotion(salaries: List[Double]): List[Double] =
    salaries.map(salary => salary * math.log(salary))

  def hugePromotion(salaries: List[Double]): List[Double] =
    salaries.map(salary => salary * salary)
}

3つのメソッドはそれぞれ掛け算の係数のみ異なることに気をつけてください。 簡潔にするため、以下のように繰り返されているコードを高階関数に抽出することができます。

object SalaryRaiser {

  private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
    salaries.map(promotionFunction)

  def smallPromotion(salaries: List[Double]): List[Double] =
    promotion(salaries, salary => salary * 1.1)

  def bigPromotion(salaries: List[Double]): List[Double] =
    promotion(salaries, salary => salary * math.log(salary))

  def hugePromotion(salaries: List[Double]): List[Double] =
    promotion(salaries, salary => salary * salary)
}

新しいメソッドpromotionはsalariesとDouble => Double型の関数(すなわち、Doubleを受け取り、Doubleを返す関数)を受け取り、積を返します。

関数を返す関数

関数を生成したい場合がいくつかあります。 こちらは関数を返すメソッドの例になります。

def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
  val schema = if (ssl) "https://" else "http://"
  (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
}

val domainName = "www.example.com"
def getURL = urlBuilder(ssl=true, domainName)
val endpoint = "users"
val query = "id=1"
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String

urlBuilderの戻り値型(String, String) => Stringに注意してください。 これは返される無名関数はStringを2つ受け取り、Stringを1つ返すことを意味します。 このケースでは返される無名関数は(endpoint: String, query: String) => s"https://www.example.com/$endpoint?$query"です。

Contributors to this page: