学習メモ:Scalaの制御構文 #Scala #dwango
この記事について
DOING
Scalaの制御構文
言葉の定義
ScalaはCやJavaなどの手続き型の言語に比べて、文よりも式になる構文が多いため、変数などの状態をできるだけ排除したわかりやすいコードが書きやすい。
構文 (Syntax)
- そのプログラミング言語内でプログラムが構造を持つためのルール
- プログラミング言語内で特別扱いされるキーワード(class, val, ifなど)
- 正しいプログラムを国姓するためのルール(クラスの中身は{}で括られる)
式 (Expression)
- プログラムを構成する部分のうち、評価することで値になるもの
1
や1+2
は数値になり、"hoge"
は文字列の値になる
文 (Statement)
- プログラムを構成する部分のうち、評価しても値にならないもの(⇔式(Expression))
val i = 1
は、定義全体としては値を持たない。(変数iは定義され,iの値は1になるけれど)
{}式
- {}構文の一般形は
{exp1; exp2; ... expN; }
- ;は改行で区切られていれば省略できる。
- exp1からexpNを順番に評価し、expNを評価した値を返す。
Scalaにおける一般的なメソッド定義
scala > def foo(): String = { "foo" + "foo" }
if式
Javaとほぼ同じだったのでスキップ
練習問題
var age: Int = 5という年齢を定義する変数とvar isSchoolStarted: Boolean = falseという就学を開始しているかどうかという変数を利用して、 1歳から6歳までの就学以前の子どもの場合に“幼児です”と出力し、それ以外の場合は“幼児ではありません”と出力するコードを書いてみましょう。
自分の回答
var age: Int = 5 var isSchoolStarted: Boolean = false if(1 <= age && 6 <= age && !isSchoolStarted){println("幼児です")}else{println("未熟児です")}
while式
Javaとほぼ同じだったのでスキップ
練習問題
do whileを利用して、0から数え上げて9まで出力して10になったらループを終了するメソッドloopFrom0To9を書いてみましょう。loopFrom0To9は次のような形になります。???の部分を埋めてください。
def loopFrom0To9(): Unit = { var i = ??? do { ??? } while(???) }
自分の回答
i++みたいなのってScala使えないんですね・・・
def loopFrom0To9(): Unit = { var i = 0 do { println(i) i += 1 } while(i < 10) }
for式
Javaの制御構文と似た使い方ができるものの、全く異なる構文です。for式の本当の力を理解するには、flatMap, map, withFilter, foreachといったメソッドについて知る必要がありますが、ここでは基本的なfor式の使い方のみを説明します。
とあるので、心してかかりたいと思います。
基本的な構文
for(ジェネレータ1; ジェネレータ2; ... ジェネレータn) A # ジェネレータ1 = a1 <- exp1; ジェネレータ2 = a2 <- exp2; ... ジェネレータn = an <- expn
- ジェネレータって言ってるのが条件っぽい(a1 <- exp1など)
- exp1〜expnには、ある数の範囲を表す式を使える
1 to 10
→1から10まで (10を含む)1 until 10
→1から10まで (10を含まない)
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
ループ変数の中から条件にあったものだけを絞りこめる
以下の例だと x!=y
が追加されたことによってx, y同じ値のものが表示されなくなっています。
scala> for(x <- 1 to 5; y <- 1 until 5 if x != y){ println("x = " + x + " y = " + y) } x = 1 y = 2 x = 1 y = 3 x = 1 y = 4 x = 2 y = 1 x = 2 y = 3 x = 2 y = 4 x = 3 y = 1 x = 3 y = 2 x = 3 y = 4 x = 4 y = 1 x = 4 y = 2 x = 4 y = 3 x = 5 y = 1 x = 5 y = 2 x = 5 y = 3 x = 5 y = 4
コレクションの要素を1つ1つ辿って処理を行うために使うことも可能
例えばListの中身の文字列をすべて出力する処理
scala> for(e <- List("A", "B", "C", "D", "E")) println(e) A B C D E
コレクションの要素を辿って、加工して新しいコレクションを作成できる
この場合ジェネレータで指定したリストの要素の前にPreが付いた要素を持つリストが新しく作成されています。
scala> for(e <- List("A", "B", "C", "D", "E")) yield { | "Pre" + e | } res9: List[String] = List(PreA, PreB, PreC, PreD, PreE)
yieldキーワードを使ったfor式はfor式ではない
- for-comprehensionと呼ぶ事がある
- yieldキーワードを使うことで、コレクションの要素を加工して返すという異なる用途に使える
練習問題
1から1000までの3つの整数a, b, cについて、三辺からなる三角形が直角三角形になるような a, b, cの組み合わせを全て出力してください。直角三角形の条件にはピタゴラスの定理を利用してください。 ピタゴラスの定理とは三平方の定理とも呼ばれ、a ^ 2 == b ^ 2 + c ^ 2を満たす、a, b, c の長さの三辺を持つ三角形は、直角三角形になるというものです。
自分の回答
^
が使えなくて微妙にハマった・・・
for (a <- 1 to 1000; b <- 1 to 1000; c <- 1 to 1000) if (a * a == b * b + c * c) { println("a=" + a + ", b=" + b + ", c=" + c) }
模範解答と違ったところ
for文の中にif文を入れ込むかfor文とif文を分けるか。
模範解答はfor (a <- 1 to 1000; b <- 1 to 1000; c <- 1 to 1000 if a * a == b * b + c * c)
で
自分の回答はfor (a <- 1 to 1000; b <- 1 to 1000; c <- 1 to 1000) if (a * a == b * b + c * c)
match式
match式は使い方によって非常に幅のある制御構造です
基本構文
- Javaのswitch-caseみたいなやつ
- Scalaはbreakしなくていい(フォールスルー動作がない)
_
はdefaultのようなもの(ワイルドカードパターン)- match式を使う際は漏れがないように、ワイルドカードパターンを利用することが多い。
マッチ対象の式 match { case パターン1 [if ガード1] => 式1 case パターン2 [if ガード2] => 式2 case パターン3 [if ガード3] => 式3 case ... case パターンN => 式N case _ => "nothing" }
パターンをまとめる
Javaのswitch-caseのときはフォールスルーさせていたけど、Scalaの場合はOR(|)でつなげられる。
"abc" match { case "abc" | "def" => println("first") println("second") }
パターンマッチによる値の取り出し
switch-case以外の使い方として、コレクションの要素の一部にマッチさせる使い方もある。
下の場合、「リストの先頭要素が"A"で5要素」のパターンにマッチすると残りが表示され、しなければ"nothing"が表示される。
scala> val lst = List("A", "B", "C", "D", "E") lst: List[String] = List(A, B, C, D, E) scala> lst match { | case List("A", b, c, d, e) => | println("b = " + b) | println("c = " + c) | println("d = " + d) | println("e = " + e) | case _ => | println("nothing") | } b = B c = C d = D e = E
+ガード式なパターンマッチもできる
さっきのプログラムにガード式(今回の場合if b != "B"
)を追加すると"nothing"が表示されるようになる。
scala> val lst = List("A", "B", "C", "D", "E") lst: List[String] = List(A, B, C, D, E) scala> lst match { | case List("A", b, c, d, e) if b != "B" => | println("b = " + b) | println("c = " + c) | println("d = " + d) | println("e = " + e) | case _ => | println("nothing") | } nothing
パターンマッチのパターンのネストが可能
- Listを要素として持つListをパターンマッチすることもできる
- パターンの前に
@
がついているのはasパターンと呼ぶ@
の後に続くパターンにマッチする式を@
の前の変数(今回の場合a)に束縛する。
- このプログラムの場合パラメータがListで先頭要素がList("A")で2要素もつ場合matchし、List(A)ともう一つの要素が表示されている
scala> val lst = List(List("A"), List("B", "C", "D", "E")) lst: List[List[String]] = List(List(A), List(B, C, D, E)) scala> lst match { | case List(a@List("A"), x) => | println(a) | println(x) | case _ => println("nothing") | } List(A) List(B, C, D, E)
パターンマッチの場合、値をとりだすことはできない
scala> (List("a"): Any) match { | case List(a) | Some(a) => | println(a) | } <console>:14: error: illegal variable in pattern alternative case List(a) | Some(a) => ^ <console>:14: error: illegal variable in pattern alternative case List(a) | Some(a) => ^
値を取り出さないパターンマッチは可能。ただこれいつ使うんだろう。型を見たい時とかだろうか・・・
(List("a"): Any) match { case List(_) | Some(_) => println("ok") }
型によるパターンマッチ
- Scalaではcaseに値ではなく型を指定することも可能
- 特定の型に所属する場合にのみマッチするパターンは
名前:マッチする型
と書く - AnyRef=JavaでいうObject型(なんでも格納できる)
- 例外処理やequalsの定義などで使うことがある
- しばしばキャストの代わりにパターンマッチが用いられるので覚えておくこと
このプログラムの場合AnyRefがStringにマッチしているので、すべてを大文字にした結果が表示されています。
scala> import java.util.Locale import java.util.Locale scala> val obj: AnyRef = "String Literal" obj: AnyRef = String Literal scala> obj match { | case v:java.lang.Integer => | println("Integer!") | case v:String => | println(v.toUpperCase(Locale.ENGLISH)) | } STRING LITERAL
JVMの制約による型のパターンマッチの落とし穴
- 型変数を使った場合、正しくパターンマッチが行われない。
- このプログラムだとcaseにList[Int]とList[String]を書いているが、パターンマッチでは区別できない
scala> val obj: Any = List("a") obj: Any = List(a) scala> obj match { | case v: List[Int] => println("List[Int]") | case v: List[String] => println("List[String]") | } <console>:16: warning: non-variable type argument Int in type pattern List[Int] (the underlying of List[Int]) is unchecked since it is eliminated by erasure case v: List[Int] => println("List[Int]") ^ <console>:17: warning: non-variable type argument String in type pattern List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure case v: List[String] => println("List[String]") ^ <console>:17: warning: unreachable code case v: List[String] => println("List[String]") ^ List[Int]
型変数を含む方のパターンマッチはワイルドカードパターンを使うと良い
obj match { case v: List[_] => println("List[_]") }
練習問題
new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList
以上のコードを利用して、 最初と最後の文字が同じ英数字であるランダムな5文字の文字列を1000回出力してください。 new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toListという値は、呼びだす度にランダムな5個の文字(Char型)のリストを与えます。なお、以上のコードで生成されたリストの一部分を利用するだけでよく、最初と最後の文字が同じ英数字であるリストになるまで試行を続ける必要はありません。これは、List(a, b, d, e, f)が得られた場合に、List(a, b, d, e, a)のようにしても良いということです。
自分の回答
うまくいかずギブアップ・・・
var i = 0 while(i <= 1000){ val lst: List[Char] = new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList lst match { case List(a, b, c, d, e) if a == e => i += 1 println(lst) } }
模範解答
問題文の
なお、以上のコードで生成されたリストの一部分を利用するだけでよく、最初と最後の文字が同じ英数字であるリストになるまで試行を続ける必要はありません。これは、List(a, b, d, e, f)が得られた場合に、List(a, b, d, e, a)のようにしても良いということです。
これを実装すると実にすっきりします。あとforかwhileどっちで回すといいとかあるのかな。
for(i <- 1 to 1000) { val s = new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList match { case List(a,b,c,d,_) => List(a,b,c,d,a).mkString } println(s) }
この章での疑問点
if文の順序
age => 1 && age <=6 && !isSchoolStarted
だとNGで
1 <= age && 6 <= age && !isSchoolStarted
だとコンパイルが通るのはなぜだろうか・・・
値を取り出さないパターンマッチの使い所
- 値を取り出さないパターンマッチは可能なのはわかったけど、これいつ使うんだろう。
進捗
DONE
TODO
- クラス
- オブジェクト
- トレイト
- 型パラメータと変位指定
- 関数
- Scalaのコレクションライブラリ
- ケースクラスとパターンマッチング
- エラー処理
- Implicit
- 型クラスの紹介
- FutureとPromise
- テスト
- Javaとの相互運用
- S99の案内
- トレイトの応用編:依存性の注入によるリファクタリング
次回はクラスをやります。次回はいつだろう・・・年内にドワンゴテキスト終わらせたい。