Hello Worldを嚙み砕こう
Hello Worldを嚙み砕こう
エントリポイント・メソッド・式と文・ブロック式
Hello World ― エントリポイント
まず先ほどsbtによって生成されたsrc/main/scala/example/ScalaTour.scalaを見ていきましょう.Scastieであれば表示されるエディタを以下に置き換え実行してください.
1: // これは一行コメントです
2: /* これは複数行コメントです
3: JavaやCと同じですね */
4:
5: // 名前空間の宣言です.多くの場合ディレクトリ階層と合わせます
6: package example
7:
8: object ScalaTour {
9: def main(args: Array[String]): Unit = {
10: val message = "Hello World"
11: println(message) // 末尾改行付き標準出力
12: /* 出力
13: Hello World
14: */
15: }
16: }
Hello Worldと出力されたら成功です.このmainというメソッドが自動的にエントリポイントとして選択され,プログラムが実行されました.
今回はmainメソッドをエントリポイントとして利用していますが,Appトレイトというものでエントリポイントを実現することもできます.以下のようにすることでリスト3.1と等価なプログラムになります.
1: package example
2:
3: object ScalaTour extends App {
4: val message = “Hello World”
5: println(message)
6: /* 出力
7: “Hello World”
8: */
9: }
本書ではより基本的であるmainメソッドを利用したサンプルで統一します.
Hello Worldの構成要素 ― メソッド・式と文・ブロック式
まず一旦外側のobjectは後回しにして(5.2節で紹介します), mainメソッドの内側から見ていきましょう.
1: val message = "Hello World"
2: // message = "Changed World" // 再代入不可能
valというキーワードから始まっているこの文は再代入ができない不変な変数の宣言です.文末にセミコロン(;)は必要ありません.Javaの
final String message = "Hello World";
に相当します.Javaの場合は文字列であること示すStringを明示しなければなりませんが,Scalaは型推論の機能があるためこれを省略できます.なお,型が定まらない場合や明示的に書きたい場合は,以下のように型注釈をつけることもできます.
1: val message: String = "Hello World"
また,以下のようにvalではなくvarというキーワードを使うことで再代入ができる可変な変数の宣言ができます.
1: var mutableMessage = "Hello World"
2: mutableMessage = "Changed World" // 再代入可能
もちろん,どちらも自由に利用できますが,基本的にはvalを利用すると良いでしょう.「ループするときのカウンタはどうするんだ?」といった疑問が湧くかもしれませんが,カウンタを利用せずループを実現するといった可変な値を利用しないで良い機能をScalaは提供しています.そのような機能や理由は追い追い紹介していきます.
次にエントリポイントであるメインメソッドを参考に,Scalaにおけるメソッドを見ていきましょう.
def メソッド名(引数名: 引数の型,...): 返り値の型 = 式
メソッドは以上の形で宣言できます.表記の順番などが多少異なりますが,他のプログラミング言語と大差がないことが解ると思います.ただし,ここで式の部分に注意してください.メインメソッドの例ならば,
1: {
2: println("Hello World")
3: }
に当たる部分です({}が含まれていることに注意してください).繰り返しになりますがこれは式です.特に{}による式をブロック式と呼びます.ここで言う式というのは,文との対比で,文は「評価のみを行って結果を返さない」一方,式は「なんらかの計算が行われて結果の値を返すもの」を指します.例えば,先ほど扱った変数の宣言であるval message = "Hello World"はmessageという変数を作成するものの,結果を返すわけではないので文です.ブロック式がどのように式になっているのかというと,{}内の文や式が順番に評価され,{}内最後の式の結果がブロック式の結果として扱われます(他の言語に多いreturnキーワードは不要です).
つまり今回の例では,println("Hello World!")が評価され,その結果であるUnit型の()がブロック式の返り値となります(printlnというメソッドは標準出力に出力するだけですが,返り値がないことを意味する()を返します.Unit型というのはJavaやCで言うvoidのようなものと考えておけばとりあえず大丈夫です).別の例として以下のようなコードを考えてみましょう.
1: val result = {
2: val x = 1 + 2
3: x * 2
4: } // `result` に 6 が代入される
この場合,まずval x = 1 + 2が評価され,次にx * 2が評価された結果(6)がブロック式全体の結果となって最終的にresultに代入されます.これらは更にブレークダウンしてみると以下のように見ることができます.
点線で囲った部分が式を,実線で囲った部分が文を表します.式や文がネストして成り立っていることが分かると思います.さてここでメソッドの定義に戻ります.
def メソッド名(引数名: 引数の型,...): 返り値の型 = 式
見直してみると,メソッドの本体は式であればなにが来ても良い,非常に自由度が高い形であることが分かります.例えば,メインメソッドの例とは違いブロック式ではない式を入れてみましょう.
1: def add(x: Int,y: Int): Int = x + y
このメソッドは引数xと引数yを加算した結果を返します.あたかも{}の省略した記法に見えますね.
1: // 値をそのまま返すだけのメソッド
2: def identity(x: Int): Int = x
といった書き方もできます.他の言語のように「括弧が必ず必要で,その中でいろいろな処理をしてreturnで返り値を指定する……」というわけではなく,とにかく式であればなんでもいいのです!
メソッド宣言の際に引数が0個の時は()ごと省略することもでき,省略して宣言されたメソッドの呼び出しには()が不要(むしろ付けるとコンパイルエラー)になります.なお,宣言時に省略していないときでも呼び出し時だけ省略することもできます.慣習的に,状態を変化させるメソッドの場合は()を付け,変化させない場合は()を省略することが多いです.本書ではわかりやすさのため,省略せずに表記しています.
【コラム】「???」を利用する
まだコードが途中で不完全,でもちょっと動かして試してみたい……ということは多々あるかと思います.Scalaはコンパイラ言語なので,基本的にはきっちりしたコードを書くまで動かせません.ですが,やっぱり動かしたいですよね?特にインタープリタ言語から来た方にはむず痒いところでしょう.
そこでScalaではデフォルトで???というものが利用できます.実態はNotImplementedError,つまり未実装を意味する例外なので実行フローに入れることはできませんが,本質的でない部分にとりあえず???を入れておくと実装(メソッドの本体,メソッドの返り値,etc.)を後回しにできます.
例えば以下のようにすると,「barメソッドに奇数が渡されて呼び出される」・「bazメソッドが呼び出される」ことがないという前提で実装を省略したままコンパイル・実行できます.言い換えると「Fooクラスのbarメソッドに偶数が渡されて呼び出される」というシチュエーションのみ適切に実行できるということです.
1: class Foo {
2: def bar(x: Int): Int = {
3: if (x % 2 == 0) x * 2 // 偶数であれば2倍して返す
4: else ??? // 奇数は未実装
5: }
6:
7: def baz(s: String): String = ??? // 未実装
8: }
(次回へ続く)