ClojureとJavaの連携

人工知能技術  デジダルトランスフォーメーション 機械学習技術  Java  確率的生成モデル  Clojure

ClojureはJVMの上で動くLispであり、様々なレベルでJavaと連携することができ、Javaの資産をフルで活用することができる。

JVMについて

JVMはJava仮想マシン(Java virtual mchine)の略となる。JVMの上で動作するJavaは、1996年にサンマイクロシステムズによってリリースされ、2010年に同社がオラクルに吸収合併された時に版権がオラクルに移り、以降オラクルにより開発が続行され、クライアント/サーバーモデルのwebアプリケーションで最も多く利用されている言語となる。

JVMが登場するまでのプログラミング言語は、下図の左に示すように、コンパイラーを通してCPUが理解できる命令に変換し実行していた。この手法で複雑な言語を構築した場合、ハードウェアのバリエーションに対する対応が困難であるという課題があった。これに対してJVMでは、Javaバイトコードと呼ばれるコンパイルファイルを一旦形成し、それを受け取ったJVMがホストが理解できる機械語に翻訳するという形式をとることで、よりフレキシブルに高度なプログラミングが可能とした。

このJVM環境を用いることで、同じJVM上で動作するScala、JRuby、JavaとClojureは高度な互換性を保つことが可能となっている。

Javaについて

Javaは以前”オブジェクト指向言語“等で述べたようなObject Oriented Programming:OOP)となる。詳細はそれらに任せるとして、OOPの基本的なプレイヤーは、クラス、オブジェクト、およびメソッドとなる。Clojureについての有用な入門書持つである”Clojure for Brave and True”によれば、このオブジェクトは、間抜けなアンドロイドに例えられ、命令に対応すること、データを保持することの2つだけしかできないものとされている。

このアンドロイドを製造する工場(ファクトリ)を考えた時、アンドロイドが理解するコマンドのセットも、アンドロイドが保持するデータのセットも、OOPの用語ではファクトリはクラス、アンドロイドはオブジェクト、コマンドはメソッドに対応する。

例えばScrayClownというファクトリ(クラス)があり、makBallonArtというコマンド(メソッド)に対応するアンドロイド(オブジェクト)を生成するとする。アンドロイドは持っている風船の数を記録し、風船の数が変わるたびにその数を更新する。バルーン数を報告するにはballonCountを使用し、バルーン数を受け取るにはrecieveBalloonsを使用する。それらの記述は以下のようになる。

ScaryClown bellyRubsTheClown = new ScaryClown(); 
bellyRubsTheClown.balloonCount();
// => 0
bellyRubsTheClown.receiveBalloons(2); 
bellyRubsTheClown.balloonCount();
// => 2
bellyRubsTheClown.makeBalloonArt();
// => "Belly Rubs makes a balloon shaped like a clown, because Belly Rubs
// => is trying to scare you and nothing is scarier than clowns."

この例では、ScaryClownクラスを利用して、新しいオブジェクトbellyRubsTheClownを作成する方法について述べている。またこのオブジェクトのメソッド( balloonCount, receiveBalloons, makeBalloonArt など )を呼び出す方法も示している。

またOOPはファクトリにコマンドを送信することもできる。これはOOPの用語的に言うと、クラスもメソッドを持つということになる。例としては、組み込みクラスのMathには、数の絶対値を返すMath.absをはじめ多くのクラスメソッドがある。

Math.abs(-50)
// => 50

ここでfacebookというディレクトリにPitatePhrases.javaを作り以下のように記述する。

public class PiratePhrases
{    
   public static void main(String[] args)
   {
      System.out.println("Shiver me timbers!!!");
   }
}

このシンプルなプログラムは、実行すると “Shiver me timbers!!!”というフレーズをターミナルに表示する(海賊が “Hello, world!”と言うのと同じ)。これはPiratePhrasesというクラスと、そのクラスに属するmainというスタティックメソッドから構成されている。スタティックメソッドとは、本来はクラスメソッドのこととなる。

ここで(JVM環境が設定されているとして)ターミナルで”javac PitatePhrases.java”と入力すると、PiratePhrases.classというクラスファイルが生成され、”java PitatePhrases”と入力すると”Shiver me timbers!!!”と表示され、正しく動作(run)させたことが確認できる。

ここで行なったことは、javacというコンパイラを使って、PitatePhrases.javaをコンパイルしてPitatePhrasesという名のクラスファイルを作っている。このクラスファイルには大量のJavaバイトコードが詰め込まれている。

cafe babe 0000 003d 001d 0a00 0200 0307
0004 0c00 0500 0601 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0100 063c 696e
6974 3e01 0003 2829 5609 0008 0009 0700

java PiratePhrasesを実行すると、JVMはまずクラスパスを見て、PiratePhrasesという名前のクラスを探す。クラスパスは、クラスを定義するファイルを見つけるためにJVMが検索するファイルシステムのパスのリストとなる。デフォルトでは、クラスパスは、javaを実行したときにいるディレクトリを含んでいる。Javaでは、1つのファイルに1つのパブリック・クラスしか許可されず、ファイル名はクラス名と一致しなければならない。こうしてjavaはPiratePhrases.classからPiratePhrasesクラスのバイトコードを探し、それを見つけると、そのクラスのmainメソッドを実行する。JavaはC言語と似ていて、「何かを実行し、このクラスをエントリポイントとして使用する」と言うと、常にそのクラスのmainメソッドを実行するものとなる。

マルチファイルプログラムとJavaライブラリの使い方

まずJavaでのパッケージはコードの整理を提供する。具体的にはパッケージはクラスを含み、パッケージ名はファイルシステムディレクトリに対応する。もし、ファイルに package com.shapemaster という行があれば、 com/shapemaster というディレクトリがクラスパスのどこかに存在していることとなる。

import Javaでは、クラスをインポートすることができる。これは基本的に、名前空間接頭辞を使わずにクラスを参照できることを意味する。つまり、com.shapemasterにSquareというクラスがある場合、.javaファイルの先頭でimport com.shapemaster.Square; または import com.shapemaster.*; と記述すれば、コード中でcom.shapemaster.Squareではなく、Squareを使用することができるものとなる。

ここでpackageとimportを使った例について述べる。ここではpirate_phrasesというパッケージを作成し、GreetingsとFarewellsという2つのクラスを作成する。まずphrasebookに移動して、そのディレクトリの中に、pirate_phrasesというディレクトリを作ります。Java のパッケージ名はファイルシステムのディレクトリに対応しているため、pirate_phrases を作成する必要がある。次に、pirate_phrasesディレクトリの中にGreetings.javaを作成する。

phrasebook
  |____pirate_phrases
           |_____Farewells.java
           |_____Greetings.java
  |_____PirateConversation.java
  |_____PirtaePhrases.java

ここでpirate_phrasesフォルダー内にあるimportするFarewells.java、Greetings.javaファイルを以下のようにする。

package pirate_phrases;

public class Farewells
{
    public static void goodbye()
    {
        System.out.println("A fair turn of the tide ter ye thar, ye magnificent sea friend!!");
    }
}
package pirate_phrases;

public class Greetings
{
    public static void hello()
    {
        System.out.println("Shiver me timbers!!!");
    }
}

さらにそれらをimportして動作させるPirateConversation.javaファイルを以下のようにする。

import pirate_phrases.*;

public class PirateConversation
{
    public static void main(String[] args)
    {
        Greetings greetings = new Greetings();
        greetings.hello();

        Farewells farewells = new Farewells();
        farewells.goodbye();
    }
}

一行目の、import pirate_phrases.*; は、Greetings と Farewells クラスを含む pirate_ phrases パッケージの全クラスをインポートする記述となる。

これらに対して”javac PirateConversation.java”でクラスファイルをコンパイルし、”java PirateConversation”で動作させると”Shiver me timbers!!! A fair turn of the tide ter ye thar, ye magnificent sea friend!!”と二つのクラスを使った動作を確認できる。

JARファイル

JARファイルを使用すると、すべての.classファイルを1つのファイルにバンドルすることができる。前述のphrasebookのディレクトリに移動して、以下を実行する。

jar cvfe conversation.jar PirateConversation PirateConversation.class

conversation.jarという圧縮ファイルが作成され、中のコンテンツは”jar tf conversation.jar”にて見ることができ、”java -jar conversation.jar”にて動作させることができる。

ClojureでのJavaクラスアクセス

Javaのコードで定義されたクラスは、名前空間で”import”することで装飾なしで直接あるいはドットフォーマットで利用することができる。

(.toUpperCase "By Bluebeard's bananas!")
; => "BY BLUEBEARD'S BANANAS!"
u(.indexOf "Let's synergize our bleeding edges" "y") 
; => 7

この他にも、”Clojureを使った自然言語処理“で述べているようにJavaのkuromojiクラスを直接利用したり、LDAやSVMなどのJavaで作られたライブラリを直接利用することができる。

前述の手法は既存オブジェクトのメソッドを呼び出す手法だが、新しいオブジェクトを作成する方法(new ClassName optinal-argesとClassName. optional-arges)もある。

(new String)
; => ""
(String.)
; => ""
(String. "To Davey Jones's Locker with ye hardies")
; => "To Davey Jones's Locker with ye hardies"

また、Java側でClojureで作ったクラスファイルをimportして利用することもできる。

コメント

タイトルとURLをコピーしました