LL脳がscalaの勉強を始めたよ その7

第4章の後半の後半:スタンドアロンシングルトンオブジェクト

コップ本の第4章残り全部をやりますよー

scalaファイルをスクリプト以外の動かし方をするためのやり方?だと思うけどどうなんだろう…

スタンドアロンシングルトンオブジェクトでmain処理

Scalaプログラムを実行するには、mainメソッドを持つスタンドアロンシングルトンオブジェクトの名前を指定しなければならないらしい。mainメソッドから実行することを”アプリケーションのエントリーポイントとして使う”と呼ぶ…でいいんだよね?チト不安。

ちなみに、ここで指定されるスタンドアロンシングルトンオブジェクトのmainは次の性質を持つ必要がありマス

  • Array[String]をパラメータとする
  • 結果型はUnit
まずは手を動かして作ってみる

HogeクラスとHogeオブジェクトを、スタンドアロンシングルトンオブジェクトmogeを使って実行してみますよー

まず、前回定義したHogeクラスとHogeオブジェクトのコンパニオンズ(造語)をHoge.scalaとして保存するのです

/*** Hogeクラスの定義(前回定義した掛け算するクラス) ***/                             
class Hoge {
  var moge = 3
  private var huga = 0
  def add(i: Int) { huga += i}
  def multiple() = huga * moge
}
/*** Hogeシングルトンオブジェクトの定義 ***/
// 可変長のmutableなMapが必要なのでインポート
import scala.collection.mutable.Map
// オブジェクト定義
object Hoge {
  // キャッシュ用のマップを定義
  private val cache = Map[Int, Int]()
  // Hogeクラスを利用した計算結果を返すメソッド
  def calc(i: Int): String =
    // 引数が以前計算した値であればキャッシュした内容を返す
    if(cache.contains(i))
      // キャッシュ内容を返す
      "cached value: " + cache(i).toString
    // キャッシュがなければ計算
    else {
      // Hogeクラスを利用して計算
      val h = new Hoge
      h.add(i)
      val result = h.multiple()
      // 計算結果をキャッシュに追加
      cache += (i -> result)
      // 計算結果を返す
      "calc value: " + result.toString
    }
}


そしてHogeオブジェクト経由でHogeクラスを利用するスタンドアロンシングルトンオブジェクトのmogeオブジェクトを定義しますよ

// Hoge.scalaファイル内のHogeオブジェクトのcalcメソッドをインポート
import Hoge.calc                            
// mainメソッドを持つmogeオブジェクトを定義
object moge {                 
  // 引数にコマンド引数をとる    
  def main(args: Array[String]){
    // Hoge.calcを引数ごとに呼び出して実行
    for(arg <- args)
      // 結果を表示                                                                                   
      println(arg + " : " + calc(arg.toInt))
  }
}

さて、定義ができたので次は実行してみますよー

定義ファイルはスクリプト実行できない件

スタンドアロンオブジェクトのような(オブジェクトやクラス等の)定義のみのファイルは、スクリプト実行できないのでコンパイルを行う必要があります。ちなみにスクリプト実行は次のように行うのデス。

scala scalaファイル名

なお、Scala的にスクリプトかどうかの判定は以下の条件による

  • ファイルの最後が結果式になっているかどうか

なので、例えばHoge.scalaの末尾に以下のような式を追加すればスクリプト実行ができる

println(Hoge.calc(1))
スクリプト実行できないのでコンパイルして実行しますー

まずはコンパイル:scalacを使います

scalac Hoge.scala moge.scala

実行:mogeアプリケーションが出来ているのでscalaコマンド経由で実行

// mogeアプリを実行
scala moge 1 2 3

// 結果
1 : calc value: 3
2 : calc value: 6
3 : calc value: 9
何度もコンパイルをする場合はfscを使おうぜ

scalacでコンパイルする場合はコンパイル準備作業を毎回行うので、何度もコンパイルしたいときはfsc使うとよいとのこと。

fscは初回起動時にローカルサーバデーモンを立ち上げてjavaランタイム起動するので、2度目以降はデーモンプロセス経由でコンパイル準備作業を省略することができるらしい。

fscでのコンパイル実行はscalacの代わりにfscをコール

fsc Hoge.scala moge.scala

fscを終了する場合は次のようにする

fsc -shutdown
Applicationトレイトを使ってみよう

スタンドアロンオブジェクトで毎回mainを書くのがめんどくさい方に朗報!!Applicationトレイトを拡張することでmainを省略できますー

Applicationトレイトを拡張したhuga.scala

// Hoge.scalaファイル内のHogeオブジェクトのcalcメソッドをインポート                                           
import Hoge.calc
// Applicationトレイトを拡張したhugaオブジェクトを定義
object huga extends Application{
  // 引数にコマンド引数をとる
    // Hoge.calcを引数ごとに呼び出して実行
    for(num <- List(1,2,3,4,5,6))
      // 結果を表示
      println(num + " : " + calc(num))
}

コンパイル実行後の結果は以下の通り

1 : calc value: 3
2 : calc value: 6
3 : calc value: 9
4 : calc value: 12
5 : calc value: 15
6 : calc value: 18

Applicationトレイトは{}内のコードを基本コンストラクターの中に集めて、クラスの初期化時に実行するためにmainを省略することができるとのこと。

ただし、Applicationトレイトは

  • args配列にアクセスできない
    • コマンド引数がとれない
  • マルチスレッドプログラムで使えない
    • マルチスレッドプログラムは明示的なmainを必要とする
  • 一部のJVMの実装ではApplicationトレイトが実行するオブジェクト初期化コードを最適化できない

と、いろいろとデメリットがあるそうな…今のところテスト実行や単発の簡易処理に使うような感じかな?…などとなんとなく思っている程度なんだけど、Applicationトレイトについては後ろの章で詳しくやるそうなのでそれを読んでからかな…と

あと現時点で、トレイト→特定の処理のためのクラステンプレート、みたいな認識をしてるんだけどあってんのかな?

Scalaのファイル名の話

上記サンプルでHogeコンパニオンズはHoge.scalaというファイルに記述したわけだけども、Scalaでは必ずしもクラス名とファイル名を同じにする必要はないみたい。

でもコップ本曰く、ファイル名からクラス名を推測できた方が便利だよね…うん、そう思う

おまけ

Scalaのソースファイルはコンパイル時に暗黙のうちにpredefパッケージをインポートする

printlnやassertがそのまま呼び出せるのはPredefパッケージのメソッドのため

printlnは実際はPredef.println ( 内部的には Console.printlnの呼出らしい)

以上ー

ようやく4章が終わり、次は演算子かー