1. 序章

このチュートリアルでは、Scalaのforループとその多様な機能セットを見ていきます。

2. Forループ

簡単に言えば、forループは制御フローステートメントです。 これにより、一部のコードを繰り返し実行できます。 コードの実行を繰り返す必要がある場合に使用できます。

forループの一般的な構文は次のとおりです。

for ( receiver <- generator ) {
    statement
}

どこ:

  • receiveer は、各反復でgeneratorから次の新しい値を受け取る変数です。
  • ジェネレーター、一般的なケースではは、範囲コレクション、またはマップ–より高度なケースについては後で説明します

2.1. 範囲の使用

ループを繰り返す回数を制御するには、 範囲。 これにより、ループカウンターも提供されます。

Range は、 Int 値の順序付けられたシーケンスであり、開始値と終了値によって定義されます。

val range = 1 to 3
val rangeUntil = 1 until 3

キーワードtoは、初期値から終了値までのRangeを定義します。

for (num <- range) {
    println(num)
}

上記を実行すると、次のようになります。

1
2
3

キーワードuntilは、同じ Range、を定義しますが、終了値はありません。

for (num <- rangeUntil) {
    println(num)
}

これにより、わずかに異なる出力が得られます。

1
2

各反復で、変数 num (ループカウンター)は、Rangeが終了するまでRangeから次の値を受け取ります。

2.2. 複数のジェネレーター

Javaではネストされたループを作成する必要がありますが、Scalaでは同時に複数のジェネレーターでforを1つしか使用できません。 前の例から定義された範囲を使用してこれを示すことができます。

for {
    i <- range
    j <- rangeUntil
} {
    println (s"$i, $j")
}

これは私たちに与えます:

1, 1
1, 2
2, 1
2, 2
3, 1
3, 2

使用した構文を見てください。 括弧の代わりに中括弧を使用する場合は、異なるジェネレーターを区切るために使用されるセミコロン(;)をスキップできます。

追加のジェネレーターごとに、独自のループカウンターを使用して内部反復がもう1つ追加されます。 その結果、合計で( range.size * rangeUntil.size = 6 )の反復があります。

2.3. コレクションを使用する

コレクションを反復処理するには、同じ構文を使用できます。

val colorList = Seq("R", "G", "B")
for (color <- colorList) {
    println(color)
}

コレクションの各要素を出力します:

R
G
B

コレクションに複数のジェネレーターを使用する方法を示すために、は、 Seq を使用して、文字「R」、「G」、「B」のすべての可能な組み合わせを3文字の単語で出力します。前に定義した

for (c1 <- colorList; c2 <- colorList; c3 <- colorList) {
    print(s"$c1$c2$c3 ")
}

これにより、すべての組み合わせが印刷されます。

RRR RRG RRB RGR RGG RGB RBR RBG RBB GRR GRG GRB GGR GGG GGB GBR GBG GBB BRR BRG BRB BGR BGG BGB BBR BBG BBB

ただし、1セットの文字がある場合、「RRR」のような組み合わせは無効です。 この場合、ガードを使用できます。

2.4. forループのガード

ガードは、forループ内で使用できる条件にすぎません。

無効な組み合わせを除外するために、前のコードにガードを追加しましょう。 また、読みやすくするために、中括弧を使用してforループを書き直します。

for {
    c1 <- colorList
    c2 <- colorList
    if c2 != c1
    c3 <- colorList
    if c3 != c2 && c3 != c1
} {
    print(s"$c1$c2$c3 ")
}

そして、出力がフィルタリングされることがわかります。

RGB RBG GRB GBR BRG BGR

c2 が次の値で満たされるたびに、 c1 と比較して、次のサブ反復をスキップできます。つまり、c2と等しい場合です。 ]c1

また、1つの for ループ内に必要な数のガードを配置でき、ガードは必要なだけ複雑にすることができることを示しました。

2.5. forループforMap

Map を反復処理する場合との違いは、レシーバーが2つの値(キーとこのキーに関連付けられた値)のTupleを取得することです。

val map = Map("R" -> "Red", "G" -> "Green", "B" -> "Blue")
for ((key,value) <- map) {
    println(s"""$key is for $value""")
}

そして結果は次のとおりです。

R is for Red
G is for Green
B is for Blue

List s Map があり、両方を繰り返す必要がある場合はどうなりますか? 確かに複数のジェネレーターを使用できます。 外側の反復では、キーと値のペアを取得し、内側の反復では、リストである値を使用します。

例として、トランプのデッキがあるとしましょう。 これはマップ、で、キーはカードのスートであり、値は各スートのランクのリストです。

val deck = Map("♣" -> List("A", "K", "Q"),
               "♦" -> List("J", "10"),
               "♥" -> List("9", "8", "7"),
               "♠" -> List("A", "K", "J", "6"))

for ループを1つだけ使用して、デッキにあるすべてのカードを印刷できます。

for {
    (suit, cardList) <- deck
    card <- cardList
} {
    println(s"""$card of $suit""")
}

マップにあるすべてのカードが印刷されます。

A of ♣
K of ♣
Q of ♣
J of ♦
10 of ♦
9 of ♥
8 of ♥
7 of ♥
A of ♠
K of ♠
J of ♠
6 of ♠

2.6. for Loop With yield

これまで検討してきたすべてのforループの例は、各反復でステートメントを実行するだけです。

ただし、コレクション内の各要素を新しいものに変換する必要がある場合は常に、forループで特別なキーワードyieldを使用する必要があります。

forループの構文は次のようになります。

val result = for ( generator ) yield {
    yield_statement
}

yieldは、結果の コレクションの新しい要素としてステートメント実行の結果を返し、forループの結果を使用して格納できます。変数。

for ループとyield、を示すために、数値のListを使用します。

val numberList = List(1, 2, 3)

最後に、 for ループを使用して、このListStringListに変換します。

val equation = for (number <- numberList) yield {
    s"""$number + $number = ${number + number}"""
}

結果の値は次のとおりです。

equation: List[String] = List(1 + 1 = 2, 2 + 2 = 4, 3 + 3 = 6)

変数equationには、実行中のステートメントの結果である String s Listが含まれています。

3. 理解のために

foryieldは、Scalaで広く使用されているツールであり、別の既知の名前であるfor-comprehensionがあります。 これは、同様の条件にさらされるすべてのコンテナタイプに適用できます。

コンテナタイプがその要素のマップ関数を提供する場合、それをfor-comprehensionで使用できます。 このコンテナタイプを複数のジェネレータで使用するには、flatMap関数も提供する必要があります。

Scalaでは、すべてのコレクションとマップがmapおよびflatMap関数の実装を提供します。

圏論では、そのようなコンテナにはモナドという名前があります。 Scala言語には、他の一般的に知られているモナドがあります: Option どちらか、およびFuture。 それらはすべてmap関数とflatMap関数の両方を実装しているため、すべてを理解するために使用できます。

たとえば、IntString。の2つのOption値を作成しましょう。

val someIntValue = Some(10)
val someStringValue = Some("Ten")

ScalaのOptionは、 mapflatMap:の両方の実装を提供するため、複数のジェネレーターを使用してそれらを理解するために使用できます。

val result = for {
    intValue <- someIntValue
    stringValue <- someStringValue
} yield {
    s"""$intValue is $stringValue"""
}

for-comprehensionの結果のタイプもOptionになります。

result: Option[String] = Some(10 is Ten)

それがどのように機能するかをよりよく理解するには、 for-comprehensionは、mapおよびflatMapコンポジションの砂糖にすぎないことを知っておく必要があります。

理解せずに同じ例を書き直すことができます。

val result = someIntValue.flatMap(intValue => someStringValue.map(stringValue => s"""$intValue is $stringValue"""))

for-comprehensionと比較して同じ結果が得られます。

result: Option[String] = Some(10 is Ten)

4. 副作用

実行内で繰り返されるコードがループ外の状態を変更する場合、「副作用」があると言えます。 この例では、印刷は副作用であるため、反復は純粋ではありません。

ループの外側の状態を変更せずに、反復の各要素を新しいものにマッピングすることは、純粋な反復です。

副作用のある反復と純粋な反復の違いを理解するのは良いことです。 コード内でエラーを見つけにくくなり、テストの妥当性が低下するため、両方の反復を混在させることはお勧めできません。 また、副作用のある反復は読みにくくなります。

5. 結論

このチュートリアルでは、Scalaでforループを使用する方法を学びました。

また、理解のために調べました。 mapおよびflatMapコンポジションの砂糖にすぎないことを忘れないでください。

いつものように、例はGitHubにあります。 サンプルでは、テストで副作用に対処する方法を詳しく見てみましょう。