読者です 読者をやめる 読者になる 読者になる

ローグウェーブソフトウェアのブログ

開発をシンプルに 安全で高品質のコードを 素早くお客様のもとへ

JavaコードをScalaから呼び出すのは簡単か?(1) - CodeBuzzから

ローグウェーブのEd StewartがCodeBuzzに投稿したScalaについての記事をご紹介します。

JMSLライブラリについては先日のリリース記事もご覧ください。 blog.roguewave.jp

目次

JavaコードをScalaから呼び出すのは簡単か?(1)

blog.klocwork.com

JMSL数値ライブラリはIMSLのアルゴリズムを純粋なJavaで実装したライブラリで、Javaコードを実行可能な環境で簡単に使うことができます。Scalaはクロスランゲージ機能の最初の例です。各言語にはそれぞれ固有の特徴や癖があり、それはその言語を初めて学ぶ人にとってとりわけ顕著に映ります。1人のJava開発者として、私がこのScala言語を学ぶ時に知ったこと - 馴染み深いJavaライブラリを呼び出すことを通じて - をお伝えします。

Scalaの基礎

この記事を読んでいる人ならおそらくすでにScalaを入手し、インストールして設定する十分なスキルがあるでしょう。以下の例では仮想環境上のCentOS 6を使用します。設定で唯一つまづいた箇所は、私が愛用しているLinuxエディタのgeditが.scalaファイルをすぐには認識しなかったことです。確かにシンタックスハイライトなしでコーディングすることは可能ですが、まさかそんなことはしたくないでしょう?私はviのショートカットを遠い昔の手の記憶の中から引っ張り出しました。新しい言語のためのシンタックスハイライトを得るには少量の定義ファイルをインストールするだけで簡単に済みました。こちらからscala.langファイルとREADMEが入手可能です。

ScalaJavaやJMSLのようなJavaライブラリとスムーズに連携するように設計されています。これはScalaJavaと同じCLASSPATH環境変数を使うことからも明らかです。したがって、もし使いたいJavaライブラリがCLASSPATHに存在するなら準備はすでにできています。CLASSPATH はスイッチの切替でコンパイル時にも実行時にも設定することができ、これもJavaと非常に類似しています。Scalaのimport文は完全修飾されたクラス名を不要にするため、読み書きが簡潔になります。構文はよく似ておりますが、大きな違いは同じパッケージ内のクラスはカンマで句切るだけで使えることです。Javaの場合はimport com.imsl.math.*で全てのクラスをインポートするか個別にクラスを指定しますが、Scalaの場合は2つだけインポートする際にはimport com.imsl.math.{PrintMatrix, LU}と書くことができます。以下の各サンプルはJMSLライブラリのcom.imsl.mathに含まれるクラスを使っています。簡単のため、import文、オブジェクトの定義、mainメソッドは最初のサンプルにのみ記述しています。

JMSLの基礎

Javaのstaticメソッド呼び出しは、オブジェクトの生成を必要としないため、最初にクロスランゲージの機能に接するのに適しています。JMSLライブラリには多くのstaticメソッドがあります。メソッドの多くは単一のシグネチャで変数を受け取り、計算して答えを返します。通常返り値は倍精度の値や配列です。Scalaはよりオブジェクト指向であるためC++Javaのような手続き型を模した形でなくシングルトンを使いますが、だからといってJavaのstaticメソッドが容易に参照されなくなる、ということではありません。数あるメソッドの中から「special function」の一つである誤差関数 erf を取り上げます。完全なコードは以下のようになります。

import com.imsl.math.Sfun

object HelloJmsl {
    def main(args: Array[String]): Unit = {
        /* easy case of static method */
        val erf = Sfun.erf(0.5)
        println("erf(0.5) = " + erf)
    }
}

このサンプルはHelloJmsl.scalaというソースファイルに記述されています。コンパイルはscalac HelloJmsl.scalaで実行され、ここでもJava同様のコマンド scala HelloJmsl によって以下のように実行されます。

[~/dev/scala]% scalac HelloJmsl.scala
[~/dev/scala]% scala HelloJmsl
erf(0.5) = 0.5204998778130465

クラスのインスタンス化もそれほど複雑ではありません。このサンプルで私はxの多項式  {5{x}^2 + 2x + 1= 0} を解こうとしています。二次式なので複素数の範囲で根は2つあります。JMSLのZeroPolynomial を使うと複素数の配列で解を返します。複素数のクラス(Complex)は自前のtoString()メソッドを持っていて結果を出力することができます。コードと結果は以下で、import文やオブジェクト定義、mainメソッド等は上のサンプルをご覧ください。

val zp = new ZeroPolynomial
val coef = Array(1.0, 2.0, 5.0)
val roots = zp.computeRoots(coef)
println("x_1 = " + roots(0))
println("x_2 = " + roots(1))
x_1 = -0.20000000000000004+0.39999999999999997i
x_2 = -0.2-0.4i

二次方程式の解の公式を使えば、この問題の解が  {x=-0.2 ± 0.4i} であることが簡単にわかり、実際にこのアルゴリズムはその値を(ただし標準的な64bit倍精度で)返しています。

配列

JMSLライブラリの多くのクラスは配列を使用します。配列に関しては特に多次元配列の場合にJavaScalaでいくつか大きな構文上の違いがありますが、使用上ほとんど同一です。たとえば多次元配列はどちらの言語でも「配列の配列」として実装されています。Javaでは数値型はプリミティブでScalaでは数値型も含め全てがオブジェクトですが、数値型やその配列をこの2つの言語の間でやりとりする際にマーシャリングを行う必要はありません。

Javaの開発者にとってScalaで配列に代入するやり方は、特にテストやプロトタイピング目的で値をハードコーディングする場合などはやや冗長に感じられるでしょう。例えばJavaでは簡単に行列(長方形の2次元配列)を定義できます。

double a[][] = {
    {1, 3, 3},
    {1, 3, 4},
    {1, 4, 3}
};

同じことがScalaの構文では

 val a: Array[Array[Double]] = Array(
    Array(1, 3, 3),
    Array(1, 3, 4),
    Array(1, 4, 3))

Arrayを繰り返すのはめんどうです。これらが整数でなく倍精度型であると指定するために型を宣言する必要があります。倍精度型の配列が必要な場合1と書く代わりに1.0と書けばこれは回避できます。これが問題になるのは本番コードを書く時というよりはテストやプロトタイピングの時ですが、それこそ私たちが多くの時間を費やす部分です。

この冗長さを減じるために、「>」というシンボルを使ってScalaにArrayという単語のエイリアスを与えることができます。非常に厳密な構文を使って言語を学んでいる開発者にとって、これは魅力的な機能に映るでしょうが、私の今回のサンプルではあまり便利だとは感じられませんでした。冗長さは最小化されますが、可読性も損なわれてしまうのです。

Scala 2.10ではマクロを作成して文字列から任意の行列を定義することができます。ドキュメントを読んだ限りではこれもまた強力で興味深い機能ですが、この言語の初心者が気軽にアクセスできるものではないようです。1年かそこらで誰かとても優秀な人たちがこの小さな問題をきれいに解決してくれるものと信じています。それまでは私はこの、冗長ながら可読性の高いコードでやっていくことにします。

JMSLライブラリのAPIは多くが1次元や2次元の倍精度配列を使用しています。幸いScalaの配列をdoubleやdouble[]の引数を受け付けるJavaのメソッドに渡す際にマーシャリングや変換は不要です。一例として小サイズの行列に対するLU分解を計算してみましょう。

val a = Array(
    Array(1.0, 3.0, 3.0), 
    Array(1.0, 3.0, 4.0), 
    Array(1.0, 4.0, 3.0))
val b = Array(12.0, 13.0, 14.0)
val lu = new LU(a)
val x = lu.solve(b)
println("LU answer = " + x(0) + "\t" + x(1) + "\t" + x(2))
new PrintMatrix("x").print(x)
println("LU condition number = " + lu.condition(a))
LU answer = 3.000000000000001     2.0       1.0
  x
   0
0  3
1  2
2  3

LU condition number = 0.015120274914089344

この例では1次元ベクトルbと2次元配列aが定義されています。この配列aはLUのコンストラクタに渡され、ベクトルbはsolve()メソッドが呼ばれたタイミングで渡されます。解は単純に3つの数値なのでそのまま出力できます。IMSLのヘルパー関数PrintMatrixも、紹介されているように複雑な行列を出力するのに便利です。

さて、基礎的な部分はカバーしました。次回は問題に応じてどのようにユーザ定義関数を作成するのか、またJMSLのチャート機能で結果を見やすくする方法を紹介しようと思います。

Ed Stewart

編集後記

いかがだったでしょうか? 続編ではさらにScalaからJMSLを活用する方法をお伝えします。

blog.roguewave.jp

簡単に記述できる軽量言語から大規模な数値統計ライブラリを呼び出す例としてはPythonからIMSL C/C++ライブラリの機能を呼び出すことができるPyNLが想起されます。

blog.roguewave.jp

blog.roguewave.jp

様々な使用環境にあったやりかたでIMSLをお楽しみいただければと思います。

blog.roguewave.jp

セールスエンジニア 柄澤(からさわ)