勉強メモ1

Rubyは以前遊びぐらいでしか触ったことがありませんが、
今回勉強したいと思い、学習を始めることになりました。

ただ、以前のように分厚い本を買って、表紙(まえがき)から最後の隅々まで
読んで写経することはしないつもりです。

時間を節約し、効率的に勉強したいと思います。

その方法として、既存のプログラミング言語に共通しそうな部分は除いて
Rubyにしかないものだけを勉強する、差分学習を取ります。

この記事はその学習メモです。

文字列中の変数

"#{変数名}"

コメント

  • 1行 : #
  • ブロック =begin ~ =end

変数

ローカル

_variable

グローバル

$variable

インスタンス

@variable

クラス

@@variable

定数

大文字。変更不可能。

多重代入

q, a, z = 1, 2, 3
q, a, *z = 1, 2, 3, 4, 5 # zには配列として代入。1, 2, [3, 4, 5]

q, a = 0, 1
q, a = b, z

ar = [3,4]
q,a = ar # 3, 4

ar = [1, [2, 3], 4] 
q, (a1, a2), z = ar # 1, 2, 3, 4

JavaでのisXXXのようなメソッドRubyでは末尾に?がつく。例)empty?

分岐

  • else ifはelsifと書く。
  • unlessはifの反対。
  • case文にはJavaと違い、色々な条件(固定値、クラス、正規表現)を指定することができる。条件はwhenに書く。

if修飾子とunless修飾子

文の右にifやunlessを書くことができる。ミニif文、ミニunless文ってことか。

a = 1
"Hi" if a > 1 # nil
"Man" if a == 1 # Man
"Hoi" unless a > 1 # Hoi

===

==に似ているが、左辺がリテラル(クラス、数値、文字列など)の場合、右辺との比較を柔軟に行う。
例えば、String == "a"だと、false。String === "a"だと、"a"がStringクラスのインスタンスという比較を行い、trueがリターンされる。

同一性

オブジェクトの値を比較するなら、==を使えばいい。厳密にオブジェクトの同一性を比較するなら、eql?を使う。
Javaとは反対ですね。

繰り返し

  • untilはwhileの反対。
  • nextはJavaのcontinue。
  • redoは同じことをもう一回繰り返す。

times

10.times do 
  print "Duke"
end

配列

names = ["Duke", "Foo"]
names.each do |n|
  puts n
end

ハッシュ

person = { name: "Duke", job: "Engineer" }
person.each do |key, value|
  puts "#{key}: #{value}"
end

ブロック

1行にまとめるときは{ ... }、複数行に分ける場合、do ... end

メソッド

  • 最後にreturn文は省略してもよい。
  • 可変長引数は引数に*argsと書く。

キーワード引数

引数名:デフォルト値

def m1(q:1, a:2, z:3)
  q + a + z
end
m1(z:5,q:3) # 10

ブロック付きメソッド

def m1
  puts "Hello!"
  yield # これがブロックを実行する。
end
m1 do
  puts "Hey Duke!"
end

# Hello!
# Hey Duke!

引数がハッシュの場合、{}を省略可能

def m(q)
  q
end

m({ "n" => "d", "t" => 5 }) # { "n" => "d", "t" => 5 }
m("n" => "d", "t" => 5) # { "n" => "d", "t" => 5 }
m(n: "d", "t": 5) # { "n" => "d", "t" => 5 }

クラス

アクセスメソッド

getter, setterのようなもの。
一々メソッドを定義してもいいが、getter/setterの数が多いと、面倒。
代わりに以下のものを使えばgetterとsetterを自動生成してくれる。

attr_reader :変数名

getter

attr_setter :変数名

setter

attr_accessor :変数名

getter + setter

クラスメソッド

class << オブジェクト ~ end形式がある。
オブジェクトにクラス名を指定すると、以下の例のようにクラスメソッドになる(Classクラスのインスタンスメソッドを追加)。
しかし、オブジェクトに特定インスタンス変数を指定すると、そのインスタンスのみにメソッドが追加される。
まるでJavascriptでオブジェクトに自由にメソッドを追加できるのと同じ感じだ。

# クラス内で定義
class DukeLab
  class << self
    def classMethod1
      "Hey"
    end
  end
end

DukeLab.classMethod1 # Hey

# 特異クラス形式で定義。既にDukeLabクラスが定義されなければならない。
class << DukeLab
  def classMethod2
    "You!"
  end
end

DukeLab.classMethod2 # You!

# クラスの外から定義
def DukeLab.classMethod3
  "Man!"
end

DukeLab.classMethod3 # Man!

# クラス内で定義
class DukeLab
  def self.classMethod4
    "Woman!"
  end
end

DukeLab.classMethod4 # Man!

定数

クラス名::変数名

アクセス制限

何も指定しなければ、public。
public, private, protectedキーワードを使ってアクセス制限を定義できる。

class DukeTest
  def m1
    "Duke1"
  end
  public :m1 #デフォルト

  def m2
    "Duke2"
  end
  private :m2
end

dt = DukeTest.new
dt.m1
dt.m2 # エラー

まとめてprivateにする

class DukeTest
  def m1
    "Duke1"
  end

  private # これより下はprivate
  def m2
    "Duke2"
  end
end

getだけ可能にする。

class DukeTest
  attr_accessor :q, :a  
  private :q=, :a= # xとyにsetできないようにする。
  def initialize(q, a)
    @q, @a = q, a
  end
end

dt = DukeTest.new
dt.q
dt.q = 8 # エラー

継承

class クラス < スーパークラス

メソッドの別名と削除

alias

別名。

undef

削除。スーパークラスメソッドを削除する時に使用。

モジュール

Scalaのtrait、Javaのdefault interfaceって感じ?
クラスでinclude モジュール名すると、そのクラスにモジュールの全てのメソッドが追加される。
モジュールをクラスに入れることをMixinという。

module Mod1
  def m1
    "variable : #{@q}"
  end
end
class Duke
  include Mod1
  def initialize(q)
    @q = q
  end
end

d = Duke.new(5)
d.m1 # variable : 5

Object.extend

特定オブジェクトにモジュールをMixinする。
module Mod1
  def m1
    "variable : #{@q}"
  end
end
class Duke
  def initialize(q)
    @q = q
  end
end
d1 = Duke.new(8)
d1.extend(Mod1)
d1.m1 # variable : 8
extendでクラスメソッドを追加
module Mod1
  def m1
    "Hi!"
  end
end
module Mod2
  def m2
    "Man!"
  end
end
class Duke
  extend Mod1 #クラスメソッド
  include Mod2 #インスタンスメソッド
end
d1 = Duke.new

d1.m1 # エラー。インスタンスメソッドではない。
Duke.m1 # Hi!

d1.m2 # Man!
Duke.m2 # エラー。クラスメソッドではない。

演算子

範囲演算子

Range.new(x, y)又はx..yを使う。

for i in 1..10
  puts i
end

(1..10).to_a # 1,2,3,4,5,6,7,8,9,10
(1...10).to_a # 1,2,3,4,5,6,7,8,9  yを除く。

演算子定義

class Apple
  attr_reader :n, :p
  def initialize(n, p)
    @n = n
    @p = p
  end
  def +@ # 単項演算子の場合、後尾に@をつける。
    "Plus"
  end
  def +(other)
    self.class.new(n + other.n,p + other.p)
  end
  def inspect
    "n : #{n}, p : #{p}"
  end
end

a = Apple.new(1, 10)
b = Apple.new(2, 20)
+a # "Plus"
a + b # n : 3, p : 30

例外

キーワードが違うだけで殆どJavaと同じ。

begin # try
  ...
rescue => ex # catch
  ...
  retry # beginに戻って再実行。無限ループに注意。
ensure # finally 
  ...
end

メソッド全体をtry-catchしたい場合、beginとendは省略可能。

def duke
  ...
  rescue => ex # catch
    ...
    retry # beginに戻って再実行。無限ループに注意。
  ensure # finally 
    ...

例外のスローはraiseキーワードを使う。

rescue修飾子

A rescue Bで、Aが失敗すると、Bが実行される。ミニtry-catchのようなもの。

File.open("duke.txt") rescue "-_-Error" # -_-Error

ブロック

処理のかたまり。

メソッドに何かを実行するブロックを渡すと、前後処理は自動でやってくれるケースが多い。
例えば、以下の場合、ファイルをオープン(前処理)し、その内容を一行ずつ表示する。
ファイルを閉じる処理(後処理)は自動でやってくれる。

File.open("README.md") do |file|
  file.each_line do |line|
    print line
    end
end

ブロックの実行

ブロックはメソッドでyieldコマンドで実行する。
yieldコマンドに引数を指定すると、ブロックに渡される。

def m1(q)
  v = yield(q + 1)
  puts "value : #{v}"
end

m1(5) { |n| "wow => #{n}" }  # value : wow => 6
m1(7) do |n| # value : haha => 7
  "haha => #{n}" 
end

制御

break, next, redoをブロック内でも使うことができる。

def m1(q)
  puts "Step1"
  yield(q)
  puts "Step2"
  yield(q + 1)
  puts "Step3"
  yield(q + 2)
end

m1(1) do |q|   # Step1 Block Step2 Block Step3 Block => 3!!!
  if (q == 5)
    break
  end
  puts "Block"
  "#{q}!!!" 
end

m1(4) do |q|   # Step1 Block Step2 => nil
  if (q == 5)
    break
  end
  puts "Block"
  "#{q}!!!" 
end

# breakやnextに戻り値を指定できる。
m1(4) do |q|   # Step1 Block Step2 => Break!
  if (q == 5)
    break "Break!"
  end
  puts "Block"
  "#{q}!!!" 
end

m1(4) do |q|   # Step1 Block Step2 Step3 Block => 6!!!
  if (q == 5)
    next
  end
  puts "Block"
  "#{q}!!!" 
end

redoは無限ループの危険がありそうだ。

Proc

ブロックをオブジェクトにしてくれるもの。"ブロックをProcオブジェクトにする"と言い方をするらしい。
Procオブジェクト化したブロックはcallメソッドで呼べる。

def m1(q, &b)
  # &はブロックをProcオブジェクトに自動変換する。
  v = b.call(q)
  "yaho!#{v}"
end

m1(5) do |q| # yaho!haha5
  "haha#{q}"
end 

myb = Proc.new do |q|
  "haha2#{q}"
end
m1(8, &myb) # yaho!haha28
m1(8, myb) # エラー。&をつける必要がある。

def m2(q, b)
  v = b.call(q)
  "kaho!#{v}"
end
myb2 = Proc.new do |q|
  "kaha2#{q}"
end
m2(9, myb2) # kaho!kaha29

#別の書き方 : procメソッド
myb3 = proc do |q|
  "kaha3#{q}"
end
m2(10, myb3) #kaho!kaha310

#別の書き方 : lambda
myb4 = lambda do |q|
  "kaha4#{q}"
end
m2(11, myb4) #kaho!kaha411

#別の書き方 : ->
myb4 = ->(q) {
  "kaha5#{q}"
}
m2(12, myb4) #kaho!kaha512

#procとlamdbaの違い1 : lamdbaの方が引数のチェックが厳密だ。
myb5 = proc do |q,a|
  print [q, a]
end
myb6 = lambda do |q,a|
  print [q, a]
end
myb5.call(2) # [2,nil]
myb6.call(2) # エラー : ArgumentError: wrong number of arguments (1 for 2)

#procとlamdbaの違い2 : procを作ったメソッド外でproc内でreturnすると、エラーになる。
def m1
  proc { return 1 }
end
def m2
  lambda { return 2 }
end
p = m1 
p.call # エラー(procが自分を作ったm1の外で呼びだされ、returnしたため) : LocalJumpError: unexpected return
d = m2 
d.call # 2

今日はここまで。

webapps配下でないディレクトリにリソースを置く

Tomcatで、ウェブアプリで使われるリソースをwebapps配下でないディレクトリに置きたい場合あります。
例えば、リソース(画像やJSPなど)をアップロードしてウェブアプリの画面を動的に構成したい時です。
またお客様がウェブアプリ本体のソースを修正することなく、カスタマイズできるようにしたい時もありますね。

もし、そういうリソースをwebapps配下におくと、
ウェブアプリをデプロイするたびに既存のリソースが消えてしまうので、困りますね。

こういう場合、Tomcat8に追加されたResources Componentを使えばいいです。
以下のようにserver.xmlのContext要素配下にResources要素とその下のPostResources要素を追加します。

<Context docBase="myweb" path="" reloadable="false">
    <Resources allowLinking="false">
        <PostResources
            className="org.apache.catalina.webresources.DirResourceSet"
            base="${other}/myres"
            webAppMount="/myres" />
    </Resources>
</Context>

PostResourcesのbaseにはリソースが位置する絶対パスを指定します。
${other}のようにJVMシステムプロパティを指定することもできます。

webAppMountにはURLのパス(ウェブアプリのルートパスで次の部分)を指定します。
上記の場合、URLの後ろに/myresと入力した場合、
baseディレクトリにあるリソースを読み込んでレスポンスとして返します。

リソースの位置がwebappsでないだけで、既存webapps配下にあるリソースとまったく同じです。
良い機能ですね。

機械学習の種類とR言語でのパッケージ

機械学習の種類とR言語でのパッケージをざっくりと整理してみました。
自分の理解に役に立つ形態で整理したため、書籍に出るようなまともなものとは違うところがあります。
また、間違った部分もありえますので、気付き次第、修正していくつもりです。

予測

分類(Classification)

データを決まったカテゴリにグループ分けすることです。

kNNアルゴリズム

classパッケージのknn関数

ナイーブベイズアルゴリズム

e1071ペッケージのnaiveBayes()関数

決定木

C5.0アルゴリズムの実装では、C50パッケージのC5.0()関数

分類規則

One Ruleアルゴリズムの実装では、RWekaパッケージのOneR()関数。
RIPPERアルゴリズムの実装では、RWekaパッケージのJRip()関数。

回帰(Regression)

数値の予測を行うことです。

線形回帰

statsパッケージのlm(関数)

回帰木

CARTアルゴリズムの実装では、rpartパッケージのrpart()関数。
M5'(prime)アルゴリズムの実装では、RWekaパッケージのM5P関数。

複数用途

神経網

人間の脳を構成するニューロンの仕組みをコンピューターで真似て予測を行います。
Neuralnetパッケージのneuralnet()関数。

SVM(Support Vector Machine)

kernlabパッケージのksvm()関数。

パターン検索

複数のものとの間にどんな関係があるかを識別します。
小売業界ではバスケット分析として使われます(この商品を買った人が一緒に買った商品)。
アプリオリ(aprioi)アルゴリズムの実装では、arulesパッケージのapriori関数。

クラスタリング

分類(Classification)と似ていますが、分類はデータを決まったカテゴリにグループ分けするのに対し、
クラスタリングは決まったカテゴリはなく、ただ単に類似したもの同士でグループ分けします。
クラスタリングで得たグループから特徴を把握し、ビジネスに使えるパターンを捕らえます。

K-Meansアルゴリズムの実装では、statsパッケージのkmeans関数。

Apache POIのExcelファイルで日本語セルの列幅を自動調整

Apache POIで列幅を自動で調整してくれる
Sheet#autoSizeColumnメソッドがあります。

数字や英語の場合、ちゃんと列幅を調整してくれるようですが
日本語の場合、Sheet#autoSizeColumnメソッドを呼び出しても、
幅がうまく調整されません。
幅が微妙に足りなかったりします。

こういう場合、セルにフォントを明示的に指定すると、
列幅が問題なく調整されます。

// ...中略...
// 日本語での列幅の自動調整のため、フォントを明示的に指定する。
CellStyle cellStyle = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("Serif");
cellStyle.setFont(font);

// ... 中略 ...
// 列幅を自動調整する。ここでは最初(0番目)の列の幅を自動調整する。
sheet.autoSizeColumn(0, true);

JDKのJavascript(nashron)で外部のJavaクラスを呼び出す方法

自分が作ったクラスや外部のライブラリを呼び出して、
機能を作りたい時があります。
普通にJavaで作ってもいいですが、
コンパイルが必要ですし、
早く作りたいのにJavaの厳格な文法がちょっと面倒です。

ScalaやGroovyなどを使って
軽くスクリプトを作るのもいいですが、
JDKに標準で入っていないから、やはり面倒。
それでJDK(バージョン8)に標準で入っているJavascriptエンジンnashronを
使ってみました。

しかし、nashronはなかなか外部クラスを認識してくれませんでした。
jrunscriptコマンド(またはjjsコマンド)の実行時に
classpathや-cpにクラスパスをjavaコマンドと同じ方法で指定したのにも関わらず、
いつもClassNotFoundExceptionが発生して困っていました。

色々と試した結果、やっと外部のJavaクラスを認識してくれたので、その方法をメモします。

まず、jrunscriptの実行時に以下のようにクラスパスを指定します。
Linux(Ubuntu)を基準に説明します。

例えば、/dukelab/libに10個のjarファイルがあって、それらをjrunscriptにクラスパスとして通したいなら、以下のように起動します。

jrunscript -cp `echo /dukelab/lib/* | tr ' ' ':'` 

ちょっと複雑で、これ以外の方法もあると思いますが、
今のところ、これで行けました。

単純にjrunscript -cp /dukelab/lib/*とすると、うまく行きません。
Linuxのシェルで"/dukelab/lib/*"が式として評価されて、
jrunscript -cp /dukelab/lib/a.jar /dukelab/lib/b.jar... のように
展開されて実行されます。
コマンドプロンプトでは空白文字で引数を分離していますから、
/dukelab/lib/b.jarがスクリプトファイルとして認識され、jrunscriptが実行されます。
当然/dukelab/lib/b.jarはスクリプトと関係ないバイナリのファイルですから、エラーになってしまいます。
それで上記の`echo /dukelab/lib/* | tr ' ' ':'`のように/dukelab/lib/*の展開後に、
trコマンドで空白文字をクラスパスの区切り文字である:に変換する処理を追加しました。
こうすると、実際には

jrunscript -cp /dukelab/lib/a.jar:/dukelab/lib/b.jar...

のようになるので、クラスパスとしてうまく認識できるようになります。

もし、jarファイルでなく、classファイルが置かれているディレクトリなら、もっと簡単です。
例えば、/dukelab/classes配下にJavaクラスがあるなら、
以下のように指定します。

jrunscript -cp /dukelab/classes

クラスパスを複数指定する時は:(コロン。Windowsでは;(セミコロン))で区切って下さい。

これで準備が終わり、Javascriptの話になりますが、
dukelab.js.test1パッケージにあるTestOneクラスと
dukelab.js.test2パッケージにあるTestTwoクラスをJavascriptで呼び出してみます。
2つのクラスはSystem.out.printlnを呼び出すごく単純なクラスなので、内容は割愛します。

var pkgs = new JavaImporter( 
    Packages.dukelab.js.test1, 
    Packages.dukelab.js.test2
);
with(pkgs) {
    print("TestOne : " + TestOne.getString());
    print("TestTwo : " + TestTwo.getInt());
}

実行すると、以下のように表示されます。

TestOne : wow!
TestTwo : great!

つまりいうと、Packagesオブジェクトの次に使うJavaクラスの完全修飾名を指定して呼び出すということです。
ただ、いつもPackages.dukelab.js.test1.TestOne.getString()...ように呼び出すのは辛いから
上記のようにwith文を使ってパッケージを省略して使うのがいいでしょう。
with文はJavascriptではスコープが曖昧になってしまうため、使わない方がいいと言われていますが、
JavascriptJavaクラスを使う時は便宜上、必須ではないかと思います。
ということで少なくともJavaの中のJavascriptでは、with文は当分健在だろうと勝手に思っています。
そのうち、新しい文法ができるかもしれませんが...

最後にJavascriptにおけるJavaクラスの呼出方法をまとめておきます。

// 完全修飾名で呼出し
print(Packages.dukelab.js.test1.TestOne.getString());

// with文でパッケージ(一つ)を省略
with (Packages.dukelab.js.test1) { 
    print(TestOne.getString());
}

// with文でパッケージ(一つ以上)を省略
with (new JavaImporter(Packages.dukelab.js.test1, Packages.dukelab.js.test2)) { 
    print(TestOne.getString());
    print(TestTwo.getInt());
}

Eclipseでマージしたくないファイルへの対処

Eclipse + Git環境で、マージに対するメモです。

マスタブランチからブランチX(Xはブランチの名前)へマージを行った時、
マージしたくないファイルだけと、
マスタブランチとブランチXとの間で、
違いが出てコンフリクトする場合があります。
例えば、環境ファイルの内容に相違な部分がある場合などです。

その時、コンフリクトしたファイルに対して、Team Synchronizing Perspectiveで、
Mark as Mergedし、コンフリクトを解消し、
中身もブランチXのHEAD、つまり元の内容にしても、
依然と米印のアイコンがつきます。

この状態でコミットすると、
コンフリクトしたファイルの中身は変わっていないのに
新しい履歴ができてしまいます。
それでも構わないなら、いいですが、
後で以前のソースを探す時、不便になるのは間違いないです。

これを解消するためには、マージしたファイル全てをコミットする前に、
マージしたくないコンフリクトしたファイルの中身を元に戻した(Replace WithでHEADに戻す)後、
Team > Advanced > Untrackします。

それからコミットが終わると、
該当ファイルに対してTeam > Add to Indexします。

こうすると、マージしたくないファイルへは何の影響を与えず、
マージを終えることができます。

コンフリクトしたファイルの数が多いと、手間かかりますが、
そういう場合は、コンフリクトが多いこと自体を解消した方がいいですね。

ErlangとJavaをつなぐ

この記事では、ErlangJavaの連携方法を説明します。

使い道

Erlangで出来ているプログラムを使いたいが、今Erlangが分かる人がいない。
でも、Javaが分かる人はいる。こういった状況でErlang + Javaが使えるのではないでしょうか。

また、Erlangがネットワークや並行処理に強い反面、
他の言語に比べてパフォーマンスがよくないところ(演算など)があるようで、
そういった部分はJavaに担当させるのもいいでしょう。

GUIRDB操作はJavaに任せて、分散処理はErlangに担当させるなどもありだと思います。

実際、Erlang + Javaではありませんが、
ある商用のメール配信エンジン(?)は、
メインをErlangにして、計算速度が必要な部分は
C言語で開発したとの記事を見たことがあります。

Jinterface

JavaからErlang プログラムを呼び出すための、Erlang 公式Java APIパッケージです。
APIは実際通信を行う部分とErlangデータタイプをJavaクラスとして実装した部分で構成されています。
このAPIパッケージを使えば、Erlangプログラムと連携できます。

jinterfaceのjarファイルは、${ERLANG_HOME}/lib/jinterface(又はjinterface-x.y.z)/privディレクトリ配下にOtpErlang.jarという名前で配置されています。

サンプル

受け取ったメッセージをそのまま返すエコサーバのようなErlangプログラムと、
そのエコサーバを呼び出すJavaプログラムを作成してみます。

まず、Erlangプログラムです。
Erlang 基礎ポイント7 - 分散処理 - DukeLabに出たサンプルソースと同じです。

-module(echo).
-export([start/0, send/2]).

start() -> register(echo, spawn(fun() -> loop() end)).

send(Msg, Receiver) ->
    Receiver ! {self(), {server, Msg}},
    receive
        {client, Res} ->
            Res
    end.

loop() ->
    receive
        {From, {server, Msg}} ->
            From ! {client, string:concat("Echo response : ", Msg)},
            loop()
    end.   

このErlangプログラムをコンパイルして、実行しておきます。
今回は、VirtualBox上に動くUbuntu 12.04で実行しました。
REPL環境上でテストもしてみます。
Javaからアクセスする時、識別に必要なため、erl起動時にnameとsetcookieの指定が必要です。
nameとsetcookieはメモしておいて下さい。

$ erl -name echoserver@vpc1.com -setcookie echocookie
(echoserver@vpc1.com)1> c(echo).
{ok,echo}
(echoserver@vpc1.com)2> echo:start().
true
(echoserver@vpc1.com)3> echo:send("Hello!!", echo). % 自分自身への呼出でテスト。
"Echo response : Hello!!"

次は、Javaプログラムです。
ビルドパスにjinterfaceのjarファイル(OtpErlang.jar)を追加して下さい。
今回は、Windows 7環境とEclipseで作成しました。
こちら側のPCにもErlangがインストールされていなければなりません。
jinterfaceのjarだけでは動かないので、注意して下さい。

package dukelab.erlangjavatest;

import java.io.IOException;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangDecodeException;
import com.ericsson.otp.erlang.OtpErlangExit;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.ericsson.otp.erlang.OtpMbox;
import com.ericsson.otp.erlang.OtpNode;

public class ErlangJavaTest {
    public static void main(String[] args) throws IOException, OtpErlangDecodeException, OtpErlangExit {
        // javanode : Javaプログラム側のホスト名です。任意で指定します。
        // echocookie : Erlangプログラム側でerl実行時、指定したクッキー名と同じです。
        OtpNode node = new OtpNode("javanode", "echocookie");
        OtpMbox mBox = node.createMbox();

        // メッセージをErlangプログラム側に送ります。
        // これをErlangプログラムで表現すると、
        // { echo, 'echoserver@vpc1.com' } ! { self(), {server, "Hello Erlang, I am Java."} }になります。
        {
            OtpErlangTuple tuple = createTuple(
                mBox.self(),
                createTuple(
                    new OtpErlangAtom("server"),
                    new OtpErlangString("Hello Erlang, I am Java.")
                )
            );
            // echo : Erlangプログラム側で登録したプロセス名です(register(echo, spawn(fun() -> loop() end)))。
            // echoserver@vpc1.com : Erlangプログラム側でerl実行時、指定したホスト名です。
            mBox.send("echo", "echoserver@vpc1.com", tuple);
        }

        // 応答を受け取ります。
        {
            OtpErlangObject result = mBox.receive();
            OtpErlangTuple tuple = (OtpErlangTuple) result;
            OtpErlangAtom atom = (OtpErlangAtom) tuple.elementAt(0);
            OtpErlangString str = (OtpErlangString) tuple.elementAt(1);

            System.out.println("Response from Erlang : " + str);
        }
    }

    private static OtpErlangTuple createTuple(OtpErlangObject... msg) {
        return new OtpErlangTuple(msg);
    }
}

その後、JavaプログラムのPCにあるhostsファイルに、
先ほどのErlang実行時にnameに指定したドメイン名とIPアドレス(Erlangを実行したPC)の対応を追加します。
例えば、以下のようにです。

192.168.56.101  vpc1.com

さあ、Javaプログラムを実行してみましょう。

Response from Erlang : "Echo response : Hello Erlang, I am Java."

できました!

整理

jinterfaceを使うと、Erlangプログラムと同じく動作するプログラムを
Javaで作成することができます。
サーバ役割のプログラムもクライアント役割のプログラムもできます。
いくつかのErlang機能に対応するJavaクラスを以下に並べます。

  • Erlang起動(Erlang仮想マシン) : OtpNode
  • メッセージをやりとりする(Erlangでの!やreceiveなど) : OtpMbox
  • タプル : OtpErlangTuple
  • 文字列 : OtpErlangString
  • アトム : OtpErlangAtom

この記事が理解できたら、後は、参考資料を読みながら、掘り下げていけばいいと思います。