Scala初心者がScalaのfor式を説明してみる

ごあいさつ

こんにちは。meviyを動かしているWebシステムを開発しています、大崎です。
実は今年の春の入社でして、これまでJavaやKotlin等を長く書いてきましたが、
DTダイナミクスに入社して初めてScalaを本格的にやることになりました。

既存コードを改修していく中で、Scalaならではのところで戸惑うシーンもありましたので、今から学習される方が少しでもスムーズに続けるように書き残したいと思います。
その中でも、まずはfor式について触れたいと思います。

また、本稿のScalaはバージョン2系となっております。

for式の基本

forと聞くと繰り返しのループ処理を思い浮かべてしまいがちです。
しかし、Scalaのfor式はもちろんその使い方もありますが、もう一つの使い方があります。
本記事ではそれを説明していきたいと思います。

まずは基本のイテレーションでのfor式から始めます。

以下のコードは数値のSeq(JavaでいうListみたいなものだと思ってください)でループを回すという、基礎的な例です。

val numbers = Seq(1, 2, 3, 4, 5)
for (n <- numbers) println(n)
// 1
// 2
// 3
// 4
// 5

フィルタリングも可能となっています。

val numbers = Seq(1, 2, 3, 4, 5)
for (n <- numbers if n % 2 == 0) println(n)
// 2
// 4

また、コレクションの組み合わせも可能です。

val numbers = Seq(1, 2, 3, 4, 5)
val alphabets = List('a', 'b', 'c')
for {
  n <- numbers
  a <- alphabets
} println(s"$n $a")
// 1 a
// 1 b
// 1 c
// 2 a
// 2 b
// 2 c
// 3 a
// 3 b
// 3 c
// 4 a
// 4 b
// 4 c
// 5 a
// 5 b
// 5 c

このような感じで、コレクションに対する繰り返しの処理を記述でき、
numbersのループの中でalphabetsのループをネストした動きとなります。

for式のもう一つの面 「for内包表記」

次に、「map/flatMapのシンタックスシュガーとしてのfor式」について説明します。
これは慣れるといいのですが、Javaエンジニアからすると一見難解です。

例えば、以下のような非同期処理を行う関数を含むコードがあったとします。
main関数 と非同期処理を行う exampleProcess関数 の2つの関数からなっています。

import java.lang.Thread.sleep
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

object Example {
  def exampleProcess(i: Int) : Future[Int] = {
    // 5秒後に10*iを返すメソッド
    sleep(5000)
    Future.successful(10 * i)
  }

  def main(args: Array[String]): Unit = {
    for {
      i <- exampleProcess(1)
    } yield println(i)
  }
}

まず、Futureを返すメソッド exampleProcess について説明します。
動きとしては、5秒後に 10*i を返すメソッドです。
ちなみに、 Future とは非同期処理の結果を保持することができるようなScalaのクラスです。

def exampleProcess(i : Int) : Future[Int] = {
  sleep(5000)
  Future.successful(10 * i)
}

これをfor式の中で使うと、以下の例のようになります。

def main(args: Array[String]): Unit = {
    for {
      i <- exampleProcess(1)
    } yield println(i)
  }

ここで出てきた <- という記号は、非同期処理の結果を取り出し左辺の変数に代入、
つまり、右辺の Future[Int]Future[] を外し中の Int を左辺 i に代入する、というような動きをします。
このようなfor式の書き方をfor内包表記と呼びます。

また、上記のfor内包表記はシンタックスシュガーであり、mapを使って書くと以下のようになります。

  def main(args: Array[String]): Unit = {
    exampleProcess(1)
      .map(i => println(i))
  }

変化した点としては、exmapleProcessの返り値となっているFutureの結果を使う箇所がmapに置き換わりました。
ただし、この規模であれば「mapで差し支えないのではないか?」と思ってしまいます。

そこで、以下のように非同期処理を複数連ね、その結果をさらに加工するといった場面を想定してみましょう。
まずはmap/flatMapを使った書き方です。

def main(args: Array[String]): Unit = {
    exampleProcess(1)
      .flatMap(i =>
        exampleProcess(i)
          .map(j => println(j))
      )
  }

次に、for内包表記を使った書き方を見てみましょう。

def main(args: Array[String]): Unit = {
    for {
      i <- exampleProcess(1)
      j <- exampleProcess(i)
    } yield println(j)
  }

いかがでしょうか。for内包表記の方が見通しは良く感じられるのではないでしょうか?
このように、非同期処理が複数必要になり、さらに前の処理の結果を次の処理の入力で使いたい、といった場面で非常に有用です。

終わりに

本記事ではScalaのfor式に関していくつかの例を紹介しました。
他にも書き方/使い道はあり全ては挙げきれておりませんが、Scalaのfor式、ひいてはScalaの魅力を少しでも感じていただけましたでしょうか?
本ブログでは今後もScalaをはじめとする、DTダイナミクスで用いられている技術やプロダクトなどの魅力を発信していきますので、興味を持っていただけると幸いです。