前提条件 : Scalaのインストールが完了し、scala
コマンドが利用できること
目次
- インタプリタの起動
- 変数の定義
- メソッドの定義
- 制御構文(
if
,while
,for
) - パターンマッチ
- 例外処理
注)必要に応じて追記するかも。コメント募集。
インタプリタの起動
scala
コマンドをファイル指定なしで実行すると、インタプリタが起動します。
Scalaコードをちょっとずつ手で書いて試したりすることができます。
また、起動時オプションとして以下をしておくと便利です(個人的な好みです)
- 非推奨警告(
-deprecation
) - 明示的に
import
すべき機能の警告(-feature
) - パターンマッチ漏れなどの警告表示(
-unchecked
)
// コンパイラ起動時
$ scalac -deprecation -feature -unchecked <file>
// インタプリタ起動時
$ scala -deprecation -feature -unchecked
入力したプログラムに対して、返り値の型と値(を文字列化したもの)が表示されます。
インタプリタでいろいろ試すときに参考にしてください。
以後scala>
プロンプトや返り値の表示は省略します。
変数
変数の宣言は以下のように行います。
// 再代入不可
val value = "hoge"
value = "fuga" //=> 再代入できないのでエラー
// 再代入可能
var variable = 10
variable = 20
variable = "fuga" //=> 型が違うのでエラー
-
val
(再代入不可) とvar
(再代入可能) の違いに注意してください。 - 一度宣言した変数には型が定義され、違う型の値を代入することはできません。
上記はString
やInt
といった型の宣言やセミコロンを省略したものです。
省略しない場合は以下のようになります(入力してみましょう)。
val value: String = "hoge";
var variable: Int = 10;
このように、入力される型が明らかな場合は省略できます。(型推論という機能です。)
【参考】Javaで書くとこうなります。
final String value = "hoge";
value = "fuga"; //=> エラー
int variable = 10;
variable = 20;
variable = "fuga"; //=> 型が違うのでエラー
【蛇足1】Scalaは代入文で返り値を持ちません。
そのため、i = j = 0
のような初期化やif( (result = func()) != null ) { … }
という判定ができません。
それぞれのステートメントを分割しましょう。
【蛇足2】インタプリタ上は便宜的に val
を付けた宣言を再度行うことで変数宣言を上書きできますが、コンパイルするときはエラーになります。
val value = "hoge"
val value = 1 // コンパイルではエラー
メソッド
メソッドはdef
キーワードで定義します。
// いろいろ省略
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
キーワードで返り値を指定できます。そうでない場合、最後に評価した値が返り値となります。
一つ目のメソッドの省略過程は以下のようになります。冗長なコードが削られていることが分かるでしょうか。
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で書くとこうなります。
int add(int a, int b) {
return a + b;
}
制御構文
if
if
はJavaでは文に分類されますが、Scalaでは式です。つまり値を返します。
if(1 == 2) {
"TRUE"
} else {
"FALSE"
} //=> "FALSE"
これを使うと、関数定義がかなり小さくなったりします。
(いわゆる三項演算子みたいな感じ)
def nextEven(i: Int): Int = if(i%2==0) i else (i+1)
nextEven(4) == 4
nextEven(5) == 6
while
while
の方は文です。値を返しません。
var i = 0
while(i < 10) {
println(i)
i += 1
}
for
for
はC言語やJavaに比べてやや特殊な構文を持っていますが、慣れてみれば結構直感的です。
// 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
に関してはオブジェクト指向編で説明しますが、ひとまずは以下の構文を見てみましょう。
// 繰り返した結果を新しい配列にする
for( i <- Array(1,2,3) ; j = nextEven(i) ) yield {
i + j
}
//=> Array(3,4,7)
for
は内部的に重要な意味を持つのですが、それはまたの機会で。
パターンマッチ
Scalaにはパターンマッチという構文があります。
CやJavaにおけるswitch-case
文とは全く違うものです。
次のように盛大にサボった素数判定を書いてみましょう。
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
みたいな存在。)
その場合には型を指定したマッチングやコレクションに応じた特殊なマッチングがあります。
少し長いですが、入力してみましょう。
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
でもパターンマッチが使える点です。
try {
throw new Exception("cause")
} catch {
case e: IllegalArgumentException => {
println("Bad arguments.")
}
case e: Exception => e.printStackTrace
} finally {
println("finally")
}
Javaのように複数回catch
を書くよりだいぶスッキリした印象があります。