kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

IOと例外の取り扱いについて:どっちが良いスタイル?

追記:Java 7以降(つまり、現在)はtry-with-resources構文があるので、それを使えばよいです。ここでは、Java 6かそれ以前のコーディングスタイルについて主に言っています。

kmizu.hatenablog.com

におけるkrxrossさんのコメント

java脳だと、「fw = new PrintWriter(new File(file))」で例外が発生したら、fwが不定のまま、finally句の「fw != null」で困ってしまう。 そうならないために、nullで初期化するという話に見えます。 多分JAVAだと、nullを代入しないとコンパイルエラーになるか、eclipsが警告を出したと思います。 scalaでも同じではないですか ? 良く解っていない超初心者より

を見て、強烈に不安になってしまったのですが、Javaだとこういうのが良いスタイルなんでしたっけ?

//Style (A)
package ex.callbyname
import java.io.File
import java.io.FileWriter
def fwloop (file: String, cond: => Boolean, update: => Unit)(body: => String) {
  var fw: FileWriter = null
  try {
    fw = new PrintWriter(new File(file))
    while (cond) {
      fw.write(body)
      update
    }
  } catch {
    case e: Exception => println("Error: " + e.getMessage())
  } finally {
    if (fw != null) {
      fw.close()
    }
  }
}

いや、このスタイル、Javaで正直結構見たことあるのですが、ファイルオープンの失敗と、ファイル書き込み中の失敗という意味の異なる例外を同じ階層で扱うという点であまりよくないというか、ファイルオープンの失敗はリカバー可能な場合が多いが、ファイル書き込み中の例外は外に投げっぱなしにすることしかできないことが多いという点で非常によくないスタイルだと思うのですがいかがでしょうか?

自分なら、このコードは、

//Style (B)
package ex.callbyname
import java.io.File
import java.io.FileWriter
def fwloop (file: String, cond: => Boolean, update: => Unit)(body: => String) {
  try {
    val fw = new PrintWriter(new File(file))
    try {
      while (cond) {
        fw.write(body)
        update
      }
    } catch {
      case e: Exception => println("Error In IO: " + e.getMessage())
    } finally {
      fw.close()
    }
  } catch {
    case e: Exception => println("Error in Open: " + e.getMesssage())
  }
}

のように書きます。この方が、異なる階層の例外を別々に処理できるうえに、ブロック付きopenのようなものを書きたい場合にもスタイルを変えずに済むからです:

import java.io.File
import java.io.FileWriter
def openForWrite(file: String)(body: PrintWriter => A): A = {
  val fw = new PrintWriter(new File(file))
  try {
    body(fw)
  } finally {
    fw.close()
  }
}

皆さんはどう思われるでしょうか?

ScalaでMLスタイルのモジュールを使ったプログラミングをする

何はともあれ以下のコードを見てください(ちなみに複素数クラスの実装は、

d.hatena.ne.jp

を参考にさせていただきました):

trait Complex {
  type T

  def re(a: T): Double

  def im(a: T): Double

  def make(re: Double): T

  def plus(a: T, b: T): T

  def minus(a: T, b: T): T

  def multiply(a: T, b: T): T

  def divide(a: T, b: T): T
}

object Complex extends Complex {
  case class C(re: Double, im: Double)
  type T = C

  def re(a: T): Double = a.re

  def im(a: T): Double = a.im

  def make(re: Double): T = C(re, 0.0)

  def plus(a: T, b: T): T = C(a.re + b.re, a.im + b.im)

  def minus(a: T, b: T): T = C(a.re - b.re, a.im - b.im)

  def multiply(a: T, b: T): T = C(a.re * b.re - a.im * b.im, a.im * b.re + a.re * b.im)

  def divide(a: T, b: T): T = {
    require(b.re != 0.0 || b.im != 0)
    val x = Math.pow(b.re, 2) + Math.pow(b.im, 2)
    C((a.re * b.re + a.im * b.im) / x, (a.im * b.re - a.re * b.im) / x)
  }
}

基本的に、

  • MLのsignature=Scalaのtrait
  • MLのstructure=Scalaのobject
  • MLのfunctor=Scalaのclass

ととらえれば、それなりにMLスタイルのモジュールをまねることができます(というのは、Scala関係の発表資料かどこかで読んだのですが思い出せない)。これは特に、Scalaが抽象型メンバを持っていることに寄っています。なお、「それなりに」と書いた通り、includeとかそのままでは真似られないものが色々あります。

追記:上のコードだと、Complex.Cでcase classの実装が参照できちゃうので、実際には

trait Complex {
  type T

  def re(a: T): Double

  def im(a: T): Double

  def make(re: Double, im: Double): T

  def plus(a: T, b: T): T

  def minus(a: T, b: T): T

  def multiply(a: T, b: T): T

  def divide(a: T, b: T): T
}

object Complex {
  val C: Complex = new Complex {
    case class C(re: Double, im: Double)
    type T = C

    def re(a: T): Double = a.re

    def im(a: T): Double = a.im

    def make(re: Double, im: Double): T = C(re, im)

    def plus(a: T, b: T): T = C(a.re + b.re, a.im + b.im)

    def minus(a: T, b: T): T = C(a.re - b.re, a.im - b.im)

    def multiply(a: T, b: T): T = C(a.re * b.re - a.im * b.im, a.im * b.re + a.re * b.im)

    def divide(a: T, b: T): T = {
      require(b.re != 0.0 || b.im != 0)
      val x = Math.pow(b.re, 2) + Math.pow(b.im, 2)
      C((a.re * b.re + a.im * b.im) / x, (a.im * b.re - a.re * b.im) / x)
    }
  }
}
object Main {
  import Complex.C
  def main(args: Array[String]): Unit = {
    val x = C.make(1.0, 1.0)
    val y = C.make(2.0, 2.0)
    println(C.plus(x, y))
  }
}

のように書くべきですね。こうすれば、抽象型メンバTがcase class Cを完全に隠蔽してくれます。

新言語Klassic作り始めました

まあタイトルの通りなんですが、とりあえずこれまでの自分の言語作成遍歴についても触れておきます。

まあ、こんな感じです(他にも色々作ったtoy言語があるのですが略)、自分が作った中で一番規模が大きいのがOnion(だいたいJava 1.4くらいまでの仕様を一通り持っていて、バイトコードを生成できる)、ついでYappという感じです。ただ、いずれもProof of Concept的な色彩が強いものであり、実用まで持っていけてないあたりが自分の飽きっぽさを示しているなあという感じです。

ただ、プログラミング言語作成をライフワーク(?)としてる自分としては、そろそろ実用に使えるプログラミング言語を作らねばということで重い腰を上げることにしました。

新言語の名前はKlassicで、オブジェクト指向関数型言語(ただし、両者のハイブリッドではなく統合したものとう意味)でかつ、メタプログラミングの機能が強力、構文にこだわる、くらいしか決めていませんが、今後がんがん開発するつもりです。

スターがあれば開発の励みにもなるので、もしよろしければお願いします(ぺこり)

github.com

何故ClassicでなくKlassicかというと、ぐぐらびりてぃを考慮してのことです。ちなみに、Matzさんが昔卒論で作った言語の名前はClassicだったそうな…。