FizzBuzz(Nパターン)をScalaで

お題:FizzBuzz(Nパターン) - No Programming, No Life
こちらのお題にScalaで挑戦。手続き脳が頑固で苦戦しました。

object FizzBuzzN {
  def main(args : Array[String]) : Unit = {
    fizzBuzzN(List(3, "Fizz", 5, "Buzz", 7, "Hoge"),100).foreach(s => println(s))
  }
  
  def fizzBuzzN(rule : List[_], count : Int) : List[String] = {
    
    def conv(c : String, x : Int, pr : List[_]) : String = {
      pr match {
        case n::s::pt => if (x % n.toString.toInt == 0) conv(c + s.toString, x, pt) else conv(c, x, pt)
        case _        => if (c == "") x.toString else c
      }
    }
    
    (1 to count).toList.map(x => conv("", x, rule))
  }
}

(8/16 追記)

toString.toInt とか汚いので少し書きなおし

object FizzBuzzN {
    def main(args: Array[String]): Unit = {
        fizzBuzzN(List(3, "Fizz", 5, "Buzz", 7, "Hoge"), 100).foreach(println(_))
    }

    def fizzBuzzN(rule: List[_], count: Int): List[String] = {

        def conv(c: String, x: Int, pr: List[_]): String = pr match {
            case (n: Int) :: (s: String) :: pt if (x % n == 0) => conv(c + s, x, pt)
            case n :: s :: pt => conv(c, x, pt)
            case _ => if (c == "") x.toString else c
        }

        (1 to count).toList.map(conv("", _, rule))
    }
}

try-with-resourcesを試してみる


少し時間ができたので、↓の記事を参考にJava7のtry-with-resourcesを試してみました。
Java7 体当たり/try-with-resources Statement - 日々常々

まあ他の言語からするとアレですが、Java6以前から比較すると地味にありがたい拡張ですね。特にcloseのチェック例外を無視できるのが嬉しい。なぜかリソース系クラスのcloseメソッドはチェック例外持ちが多いので、closeの例外処理が不要でも、finallyブロック内にtryをネストしなきゃいかんというのは気分の悪いものでした。

実行順が気になる

ところで、tryブロック内で例外がthrowされた場合、リソースがクローズされた後にcatchブロックが実行されます。従来であれば、closeはたいていfinallyブロックに記述されたので、catchブロックが実行されたあとにリソースがクローズされていました。

JDBCを使ったDB更新処理では、例外が発生するとcatchブロックの中でrollbackする実装をよく見かけますが、java.sql.Connectionをtry-with-resourcesに管理させた場合は。catchブロックでConnection#rollback()を呼んでも、すでにそのConnectionはクローズされている、ということになります。

Connection conn;
try (conn = dataSource.getConnection()) {
  
  // DBを読んだり書いたり〜

  conn.commit();

} catch (Exception e) {
  conn.rollback(); // ここでは、connはクローズ済みなので例外がthrowされる!
} 

まあ、JDBCの仕様上、close時に内部でrollbackされる約束になっているので、rollbackだけなら書かなければいいだけですが、システムによっては、DB上のログテーブルにエラー情報を書け、なんてケースもあるので、そういう場合は注意が必要です。そもそもDB処理で例外が発生した先でDBへ書き込めなんて、そんな実装は窓から投げ捨てるべき

まあ、大した話ではありませんが、実装パターンが若干変わってくるので、コピペテンプレ実装に慣れきっている向きは、気に留めておいたほうがいいかも知れません。

おまけ

try() の中に書けるリソースオブジェクトは、今回新設されたAutoCloseable型でなければいけません*1が、古いライブラリでも、void close() メソッドさえ持っていれば使えるようにラッパクラスを書いてみました。需要があるか微妙ですが。

import java.lang.reflect.*;

public class ACWrapper<T> implements AutoCloseable {

    private final T resource;
    private final Method closeMethod;
    
    public ACWrapper(T resource) {
        
        try {
            closeMethod = resource.getClass().getMethod("close");
        } catch (Exception e) {
            throw new IllegalArgumentException("close-method not found.", e);
        }
        this.resource = resource;
    }

    @Override
    public void close() throws Exception {
        closeMethod.invoke(resource);
    }
    
    public T getResource() {
        return resource;
    }

}

こんな感じで使います。

try (ACWrapper<OldResource> acw = new ACWrapper<OldResource>(new OldResource())) {
    OldResource res = acw.getResource();
    
    // OldResourceを使った処理
}

やっぱ型推論ほしいな…


(8/2 追記)

Java7では型引数を取るクラスのインスタンス生成・初期化の際、右辺の型引数を省略できることを忘れていました。上記の使用例のtry()の行は以下のように書けます。

try (ACWrapper<OldResource> acw = new ACWrapper<>(new OldResource())) {

ちなみにACWrapperのあとの「<>」をつけないとWarnが出ます。…それくらい補完してよ

*1:標準APIのリソース系クラスはほとんどAutoCloseableを実装しています

ScalaでJUnitテストケースを書く

とりあえず、Scala用のテストフレームワークは使わず、素のJUnitを使った例。

Eclipseから使う場合、JUnit関連クラスのimportはクラススコープの外側に書いておかないと、右クリックからテスト実行できないようです。

あと、Scala2.8からアノテーションのパラメータの書き方が変わったようで、Javaと同じような記述ができるようになりました。(2.8で、前と同じ書き方をするとコンパイルエラーになります)

import org.junit.{ Test, After, Before }
import org.junit.Assert._


class SampleTest {

    @Before
    def befor = {
        // 前処理
    }

    @Test(timeout=500)
    //@Test{val timeout=500}  //Scala2.7以前ではこう書く
    def testHoge = {
    	Thread.sleep(300)
        assertEquals(1, 1)
    }

    @After
    def after = {
        // 後処理
    }

}

Scalaの文字列処理とかパターンマッチングの便利さを考えると、JavaのテストケースもScalaで書いたほうが楽かもしれない。

(2010/09/14追記) 例外のテスト

例外の発生をテストする場合、Javaでは@Testアノテーションに下のように書いていた。

@Test(expected=IllegalArgumentException.class)

Scalaの場合は以下のようになる。

@Test(expected=classOf[IllegalArgumentException])

Monadについて

勉強中。あとでまとめる。

(2010/09/06)ざっくりまとめ

オブジェクト指向・手続き型脳味噌で理解したモナド(の概念の一部)

参考資料

All About Monads http://www.sampou.org/haskell/a-a-monads/html/index.html

モナドは象だ」の翻訳まとめ - {Fight the Future => じゅくのblog} http://d.hatena.ne.jp/jyukutyo/20081111/1226392144

EclipseのJavaプロジェクトや動的WebプロジェクトでScalaソースを混在させる

EclipseでのScala+Webアプリ開発 」の件で、とりあえずScalaコードを混ぜ込んでもビルドされるようになったので、方法の簡単に書いておきます。ただしちゃんと検証できてないので真似する際は自己責任で。

  1. Scala IDE for Eclipse (Scala Plugin)をインストールしておく。
  2. Eclipseを終了させ、対象プロジェクトのフォルダにある .project ファイルを開く。
  3. buildSpec要素内にある、javabuilderの設定を scalabuilderに置き換える

    <!-- javabuilderをコメントアウト
        <buildCommand>
          <name>org.eclipse.jdt.core.javabuilder</name>
          <arguments>
          </arguments>
        </buildCommand>
    -->
        <!-- 以下追記 -->
        <buildCommand>
          <name>org.scala-ide.sdt.core.scalabuilder</name>
          <arguments>
          </arguments>
        </buildCommand>


  4. nature要素内に、scalanatureの設定を追加する。

      <natures>
        <nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
        <nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
        <nature>org.eclipse.wst.common.project.facet.core.nature</nature>
        <nature>org.eclipse.jdt.core.javanature</nature>
        <nature>org.eclipse.wst.jsdt.core.jsNature</nature>
        <nature>org.scala-ide.sdt.core.scalanature</nature> <!-- 追記 -->
      </natures>


  5. Eclipseを起動する

scalabuilderは、Javaソースを見つけると処理をjavacに渡してくれるようなので、javabuilderはコメントアウトでOKです。

機能のテンプレ化に関する考察

大規模システムのフレームワークとか作っていると、まずは「機能の標準化」というお題目で、画面の流れのモデルケースを作り(例:検索→一覧→選択して編集→DB更新→検索画面へ戻る)、個別機能の設計ではこのモデルケースに極力準拠してもらいつつ、実装作業が始まる前にモデルケースの実装例をテンプレとして提示する、ということをよくやります。

ただ、これを素直にやっちゃうと、テンプレをコピペした機能が何十と出来上がっちゃって、標準そのもののバグや仕様変更があると莫大な修正工数がかかる、ということになりがちです。

テンプレをコピペさせるくらいなら、フレームワークAPIに封じて、個別に書く部分(画面やSQL、独自の論理チェックなど)を極小化することを考えたほうがよいのは、ちょっと考えればわかることですね。

この辺を突き詰めたのがSpring Rooだったりするわけですが、本ブログでは、一歩下がって、「プロジェクト毎モデルケースのフレームワーク化」を容易にし、かつカスタマイズ柔軟性を確保する方向で考えてみます。ScalaにおけるTraitのしくみがうまく使えそうな予感。

EclipseでのScala+Webアプリ開発

Praiades All-In-OneにScala Plugin 2.8.0.finalを入れて試してるのだけど、これってScalaプロジェクトじゃないとScalaソースをコンパイルしてくれないのでしょうか?

Dynamic Web プロジェクトを作成して、ソースフォルダにScalaソースを置くと、javacでコンパイルされて、Javaシンタックスエラーが出てしまいます。

どうせ実行するのはどちらもJavaバイトコードなんだから、混在しても判別してコンパイルしてくれればいいのに……

(8/20追記) こちらで解決