ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Scalaの勉強 - 制御構文(1)

スポンサーリンク

引き続きScalaのお勉強を。
記法の章は各種文法をどう記載するかを書いてあるだけなので飛ばして、今日はScalaの制御構文の章を。

制御構文

「構文」、「式」、「文」とは

Scalaの文法についてを把握する前に、「構文」、「式」、「文」の用語の解説があったのでこれについてまとめておく。

  • 構文(Syntax) : プログラム言語内においてプログラムが構造を持つためのルール。前回出てきたUserクラスの記述全体がこれに当たる。
  • 式(Expression) : プログラムを構成する部分のうち評価が成功すると値になるもの。例えば1 + 1など。
  • 文(Statement) : 式とは逆に評価しても値にならないもの。Scalaの変数定義val i = 1は文

ブロック式

Scalaでは{}で複数の式を囲むと、それ全体が式となる。

読み進めているドワンゴScala資料ではこの{}で括られた式を便宜上「ブロック式」と呼称する。

{ <式1>(;|<改行>) <式2>(;|<改行>) ... }

Scalaの言語仕様としてはただBlocksと書いてある(6.11 Blocks)。

BlockExpr  ::=  ‘{’ CaseClauses ‘}’
             |  ‘{’ Block ‘}’
Block      ::=  BlockStat {semi BlockStat} [ResultExpr]

下記の例では、ブロック式中の最後の式1 + 2がブロック式の値となる。

scala> { println("A"); println("B"); 1 + 2; }
A
B
res0: Int = 3

上記ブロック式の構文に従うとメソッドの呼び出しprintlnも式になるがそれでいいのか気になったので別に試してみた。

scala> { println("A"); println("B");}
A
B

とりあえず、上記のようにprintlnだけでブロック式を終わらせると1 + 2のようにres0: Int = 3という結果が表示されない。

探してみると以下の記事で丁寧に解説してくださっていた。

qiita.com

printlnなどの一見、値を返さなそうなメソッドがあります。しかしScalaではメソッド呼び出しは、式として判別されるため、意味のある結果を返さないUnit型として判断されます。

とのこと。このようなものの結果型を調べるためには:tを式の前につけるといいらしい。

scala> :t println("B");
Unit

なるほど、確かにUnitが返ってきた。

if式

「if文」ではなく「if式」なのね。形式はJavaと一緒(らしい)。

C言語とかとも一緒。

if '('<条件式>')' <then式> (else <else式>)?
  • 条件式Boolean
  • else項は省略可能
scala> var age = 18
age: Int = 18

scala> if(age < 18) {
     |    "18歳未満です"
     |  } else {
     |    "18歳以上です"
     |  }
res1: String = 18歳以上です

上記で書いたようにif式なので値が返却される。else項を省略した場合は先程紹介したUnitが補われた形で扱われる。このUnitJavaではvoidに相当するもので返すべき値が無いときに使用され()を値として持つ。

以下、ScalaのStandard LibraryのUnitの項目からの引用。

Unit is a subtype of scala.AnyVal. There is only one value of type Unit, (), and it is not represented by any object in the underlying runtime system. A method with return type Unit is analogous to a Java method which is declared void.

練習問題

var age: Int = 5という年齢を定義する変数とvar isSchoolStarted: Boolean = falseという就学を開始しているかどうかという変数を利用して、 1歳から6歳までの就学以前の子どもの場合に“幼児です”と出力し、それ以外の場合は“幼児ではありません”と出力するコードを書いてみましょう。

解答

scala> var age: Int = 5
age: Int = 5

scala> var isSchoolStarted: Boolean = false
isSchoolStarted: Boolean = false

scala> if((1 <= age) && (age <= 6) && (!isSchoolStarted)) {
     |   println("幼児です")
     | } else {
     |   println("幼児ではありません")
     | }
幼児です

while式

while式もJavaC言語と一緒。

while '(' <条件式> ')' 本体式

if式と一緒でwhileも式になる。返却すべき値がないのでUnit型の()が返却される。

scala> while (i <= 10) {
     |     println("i = " + i)
     |     i = i + 1
     | }
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10

do while式もある。

scala> do {
     |     println("i = " + i)
     |     i = i + 1
     | } while (i < 10)
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

Scalaにはbreak文やcontinue文は無いらしい。ただ後ほど扱う高階関数を使えばあまり必要にならないとのこと。

練習問題

do whileを利用して、0から数え上げて9まで出力して10になったらループを終了するメソッドloopFrom0To9を書いてみましょう。loopFrom0To9は次のような形になります。???の部分を埋めてください。

def loopFrom0To9(): Unit = {
  var i = ???
  do {
    ???
  } while(???)
}

解答

def loopFtom0To9(): Unit = {
    var i = 0
    do {
        println("i = " + i)
        i = i + 1
    } while (i < 10)
}

for式

構文は以下の通り。

for '(' (<ジェネレータ>;)+ ')' '<本体>' 

ジェネレータを1つ以上使うことが出来る。またジェネレータは以下の通り。

<ジェネレータ> = x <- <式>

言語仕様によると、Guard条件も含めれるみたい。

Expr1          ::=  ‘for’ (‘(’ Enumerators ‘)’ | ‘{’ Enumerators ‘}’)
                       {nl} [‘yield’] Expr
Enumerators    ::=  Generator {semi Generator}
Generator      ::=  Pattern1 ‘<-’ Expr {[semi] Guard | semi Pattern1 ‘=’ Expr}
Guard          ::=  ‘if’ PostfixExpr

さっそく例を。

scala> for (x <- 1 to 5; y <- 1 until 5) {
     |      println("x = " + x + " y = " + y)
     | }
x = 1 y = 1
x = 1 y = 2
x = 1 y = 3
x = 1 y = 4
x = 2 y = 1
x = 2 y = 2
x = 2 y = 3
x = 2 y = 4
x = 3 y = 1
x = 3 y = 2
x = 3 y = 3
x = 3 y = 4
x = 4 y = 1
x = 4 y = 2
x = 4 y = 3
x = 4 y = 4
x = 5 y = 1
x = 5 y = 2
x = 5 y = 3
x = 5 y = 4

for式は他の言語とはだいぶ違って独特。ジェネレータを複数持てるのでループ構造がシンプルになるのはいい感じ。pythonzip関数が一番近いかな。ちなみにtountilの違いは以下のように示す範囲の違いにあるようだ。

表現 範囲
1 to 10 1から9まで
1 until 10 1から10まで

以下のようにList(コレクションの一つ)を一つずつ辿って処理することも出来る。

scala> for(e <- List("A", "B", "C", "D", "E")) println(e)
A
B
C
D
E

perlpythonでよく使う形のループ処理も可能ということか。これだけでも結構強力な処理が可能だな。

上記のコレクションを加工することにも使えるという例もあった。

scala> for(e <- List("A", "B", "C", "D", "E")) yield {
     |   "Pre" + e
     | }
res0: List[String] = List(PreA, PreB, PreC, PreD, PreE)

yieldを使うことで、”コレクションの要素を加工して返す”という処理にすることが出来て、この動きをfor-comprehensionと呼ぶことがある。

練習問題

1から1000までの3つの整数a, b, cについて、三辺からなる三角形が直角三角形になるような a, b, cの組み合わせを全て出力してください。直角三角形の条件にはピタゴラスの定理を利用してください。 ピタゴラスの定理とは三平方の定理とも呼ばれ、a ^ 2 == b ^ 2 + c ^ 2を満たす、a, b, c の長さの三辺を持つ三角形は、直角三角形になるというものです。

解答

for (a <- 1 until 1000; b <- 1 until 1000; c <- 1 until 1000) {
    if ((a * a) == (b * b) + (c *c)) {
        println("(a, b, c) = (" + a + ", " + b + ", " + c + ")")
    }
}

愚直にif式使って書いたら、解答はGuard条件を使ったもっとスマートなものだった。

ひとつ気になってScalaのべき乗の書き方を調べてみたが、python**のように演算子を使って書くものはなくMathライブラリを使うみたい。

この章は残りはmatch式のみだが結構な分量がありそうなので今日はここまでにして残りは次回。