Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scala 3のシンタックスではforループでdoを使うことがある(カッコ付けたければ不要)

こういうツイートを見かけた。

言われてみれば、たまにScala 3のコードサンプルでそういうの見たことあるな~と思った。ので調べたメモ。

tl;dr

  • 値を返したいときはyieldを書く e.g. for x <- xs yield x * 2
  • 副作用だけが目的ならdoを書く
    • ジェネレータのカッコを明に書くならdoは省略できる e.g. for (x <- xs) println(x)
    • ジェネレータのカッコを省略するならdoを明に書かなければならない e.g. for x <- xs do println(x)

用語

  • ジェネレータ
    • Scalaのforにおいて、for x <- xs do println(x) のうち x <- xsの箇所のこと。

The code i <- ints is referred to as a generator.

Control Structures | Scala 3 — Book | Scala Documentation

forループ

doを使うパターンでは、こういう感じで書かれている:

for x <- 1 to 100 do println(x)
// 1
// 2
// 3
// ...

一方、Scalaのfor式といえばyieldを使うのがおなじみだ:

for x <- 1 to 100 yield x * 2
// => Seq(2, 4, 6, 8, ...

で、実際のところどうなのか調べてみたところ、キーとなるのは値を利用するかどうかであった。

ループではdoを書くように?

公式ドキュメントのControl Structures | Scala 3 — Book | Scala Documentationによれば、forループを行うときはfor ... do ...を利用せよ、とある。

Scala 2では以下のように書けていたコードが……

for (i <- ints) println(i)

Scala 3では以下のように書く必要がある(カッコは任意なので、カッコ付けてもカッコ付けなくてもよい):

for i <- ints do println(i)

Scala 2系から3への差分は以下の通りだ:

  • ジェネレータを包むカッコは省略できる
  • ループさせる箇所にdoを書く必要がある

カッコ付けてdoを書かないか、またはカッコ付けずにdoを書くか

コンパイラのドキュメントによれば、ジェネレータ部分がカッコで包まれているときはdoを書かなくても良いようだ。

dotty.epfl.ch

ForExpr           ::=  ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr     ForYield(enums, expr) / ForDo(enums, expr)
                    |  ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr
                    |  ‘for’     Enumerators0          (‘do‘ | ‘yield’) Expr

言い換えると、カッコを書かない場合はdoを書かなければならない。値を再利用したい場合はカッコと無関係に常にyieldを書く。

yield

さて、これだけだとyieldの出番が無い。yieldはいつ使うのか?その答えは値を利用するかどうかにかかっている。

前述のコードをもう一度持ってこよう。

for i <- ints do println(i)

このコードの返り値はUnitだ。

% scala-cli
scala> val x = for i <- ints do println(i)
// ...
scala> :t x
Unit

旧来のScalaユーザにとってはお馴染だが、ループした結果の値を再利用したい場合(そちらのほうが多いだろう)には、doではなくyieldを利用する:

// 再掲
for x <- 1 to 100 yield x * 2
// => Seq(2, 4, 6, 8, ...

こう書くと、ループごとにx * 2の結果が集められ、最終的にSeq[Int]の値が返される。

まとめ

ループだけが目的の場合は記法のバリエーションがあることがわかった。といっても、Scalaでforを扱う場合、値を集めてくる(== yieldを使う)ことが多いと思う(たいていの文脈でfor*と呼称されるのはそのためだ)。 自分も普段yieldしか使わないので、このような書き方があることを知らなかった。

カッコを使うか使わないかだけれど、正直好みの問題だと思う。自分は伝統的にカッコを使ってきたけど、こだわる必要もないと思う。今度からはカッコを外してみようかな。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?