Scala紹介 1.REPLと文法


前提条件 : Scalaのインストールが完了し、`scala`コマンドが利用できること

## 目次

* インタプリタの起動
* 変数の定義
* メソッドの定義
* 制御構文(`if`,`while`,`for`)
* パターンマッチ
* 例外処理

注）必要に応じて追記するかも。コメント募集。

## インタプリタの起動

`scala`コマンドをファイル指定なしで実行すると、インタプリタが起動します。
Scalaコードをちょっとずつ手で書いて試したりすることができます。

また、起動時オプションとして以下をしておくと便利です（個人的な好みです）

* 非推奨警告(`-deprecation`)
* 明示的に`import`すべき機能の警告(`-feature`)
* パターンマッチ漏れなどの警告表示(`-unchecked`)

```console
// コンパイラ起動時
$ scalac -deprecation -feature -unchecked <file>

// インタプリタ起動時
$ scala -deprecation -feature -unchecked
```
入力したプログラムに対して、返り値の型と値(を文字列化したもの)が表示されます。
インタプリタでいろいろ試すときに参考にしてください。

以後`scala> `プロンプトや返り値の表示は省略します。

## 変数

変数の宣言は以下のように行います。

```scala:Value,Variable
// 再代入不可
val value = "hoge"
value = "fuga" //=> 再代入できないのでエラー

// 再代入可能
var variable = 10
variable = 20
variable = "fuga" //=> 型が違うのでエラー
```

* `val`（再代入不可） と `var`（再代入可能） の違いに注意してください。
* 一度宣言した変数には型が定義され、違う型の値を代入することはできません。

上記は`String`や`Int`といった型の宣言やセミコロンを省略したものです。
省略しない場合は以下のようになります（入力してみましょう）。

```scala:
val value: String = "hoge";

var variable: Int = 10;
```
このように、入力される型が明らかな場合は省略できます。（型推論という機能です。）

【参考】Javaで書くとこうなります。

```java:Java比較
final String value = "hoge";
value = "fuga"; //=> エラー

int variable = 10;
variable = 20;
variable = "fuga"; //=> 型が違うのでエラー
```

【蛇足１】Scalaは代入文で返り値を持ちません。
そのため、`i = j = 0`のような初期化や`if( (result = func()) != null ) { … }` という判定ができません。
それぞれのステートメントを分割しましょう。

【蛇足２】インタプリタ上は便宜的に `val` を付けた宣言を再度行うことで変数宣言を上書きできますが、コンパイルするときはエラーになります。

```scala:OnlyForInterpreter
val value = "hoge"
val value = 1     // コンパイルではエラー
```

## メソッド

メソッドは`def`キーワードで定義します。

```scala:MethodDefinition
// いろいろ省略
def add(a: Int, b: Int) = a + b

add(1,2) //=> 3

// 省略しない場合
def twice(a: Int): Int = {
  return a * 2;
}

twice(3) //=> 6

// 引数にデフォルト値指定
def hello(s: String = "world") = {
  println("Hello," + s + "!")
}

hello()        //=> "Hello,world!"
hello("Scala") //=> "Hello,Scala!"
```

* 文法は `def <メソッド名>(<引数>: <引数の型>): <返り値の型> = <実装>` となります。
* `return`キーワードで返り値を指定できます。そうでない場合、最後に評価した値が返り値となります。

一つ目のメソッドの省略過程は以下のようになります。冗長なコードが削られていることが分かるでしょうか。

```scala:Scala
def add(a: Int, b: Int): Int = { return a + b; } // 省略なしバージョン
def add(a: Int, b: Int) = { return a + b; } // 返り値の型は明らかに Int なので省略
def add(a: Int, b: Int) = { return a + b }  // セミコロン省略
def add(a: Int, b: Int) = { a + b }         // 最後に評価した値が返り値(return不要)
def add(a: Int, b: Int) = a + b             // 1文なら括弧不要

def const = "hoge" // 引数なしの場合は括弧も省略可
```

【参考】`add`をJavaで書くとこうなります。

```java:Java比較
int add(int a, int b) {
  return a + b;
}
```

## 制御構文

### if

`if`はJavaでは文に分類されますが、Scalaでは式です。つまり値を返します。

```scala:
if(1 == 2) { 
  "TRUE"
} else {
  "FALSE"
} //=> "FALSE"
```

これを使うと、関数定義がかなり小さくなったりします。
（いわゆる三項演算子みたいな感じ）

```scala:
def nextEven(i: Int): Int = if(i%2==0) i else (i+1)

nextEven(4) == 4
nextEven(5) == 6 
```

### while

`while`の方は文です。値を返しません。

```scala:
var i = 0
while(i < 10) {
  println(i)
  i += 1
}
```

### for

`for`はC言語やJavaに比べてやや特殊な構文を持っていますが、慣れてみれば結構直感的です。

```scala:for
// 1から3を順に処理
for( i <- 1 to 3 ) {
  println(i)
}
//=> 1
//   2
//   3

// フィルタリング
for( i <- 1 to 6 ; if i%2 == 0) { 
  println(i)
}
//=> 2
//   4
//   6

// 繰り返しごとに一時変数を定義
for( i <- 1 until 4 ; j = i * 2 ) { 
  println(j)
}
//=> 2
//   4
//   6

// 複数の入力元を指定(入力して結果を確認してみましょう)
for {
  i <- 1 to 3
  j <- 1 to 4
} println(i,j)
```

また、`yield`を指定することで、処理結果を新しいコレクションにして返すことができます。
配列のクラス`Array`に関してはオブジェクト指向編で説明しますが、ひとまずは以下の構文を見てみましょう。

```scala:for
// 繰り返した結果を新しい配列にする
for( i <- Array(1,2,3) ; j = nextEven(i) ) yield {
  i + j
}
//=> Array(3,4,7)
```

`for`は内部的に重要な意味を持つのですが、それはまたの機会で。

## パターンマッチ
Scalaにはパターンマッチという構文があります。
CやJavaにおける`switch-case`文とは全く違うものです。

次のように盛大にサボった素数判定を書いてみましょう。

```scala:PatternMatch
def checkPrime(n: Int) = n match {
  case i if i < 1    => "non-positive number" // if : 条件指定
  case 2 | 3 | 5 | 7 => n.toString + " is prime" // 複数の値の or
  case i             => i.toString + " is unknown" // それ以外
}

checkPrime(-1)
checkPrime(5)
checkPrime(6)
```

入力値が幾つもの型で来る場合もあります。(`Any`はどんな型でも受け取れます。Javaの`Object`みたいな存在。)

その場合には型を指定したマッチングやコレクションに応じた特殊なマッチングがあります。

少し長いですが、入力してみましょう。

```scala:PatternMatch
def matchTest(a: Any): String = a match {
  case s: String => "Detected some String: " + s // String型にマッチ
  case x :: Nil  => "last element of List" // List型の最後の要素にマッチ
  case x :: xs   => "an element of List, " + matchTest(xs) // List型にマッチ
  case _         => "Another one" // それ以外(値は捨てる)
}

matchTest("hogehoge")
matchTest(List(1,2,3))
matchTest(Array(1,2,3))
```

このように、パターンマッチは具体的な値や型、リスト分解(コレクションのところで詳しく紹介する予定です)、ワイルドカード(`_`:アンダースコア) などで指定出来ます。

パターンマッチの説明は後々、`case class`の説明でも出てくるのでここまでにします。

## 例外処理

Javaの場合は例外を返す可能性がある場合 `throws` で宣言する必要がありましたが、Scalaでは`throws`で指定することができません。(毎度毎度throwsもしくは`try-catch`でチェックするのは面倒ですよね。その代わり、Java互換性のために`@throws`というアノテーションがあります。)

実際にチェックしたい場所では`try`を指定できます。
Javaと違うのは、`catch`でもパターンマッチが使える点です。

```scala:ExceptionHandling
try {
  throw new Exception("cause")
} catch {
  case e: IllegalArgumentException => {
    println("Bad arguments.")
  }
  case e: Exception => e.printStackTrace
} finally {
  println("finally")
}
```
Javaのように複数回`catch`を書くよりだいぶスッキリした印象があります。