はじめに
PrologCafeとはJava言語で実装されたProlog言語の処理系である。Prolog言語のソースコードをJava言語に変換したり、Java言語でProlog言語の項や節や述語を記述したりして、それらをJVMで実行できる。
PrologCafeは主に以下のクラスから構成されている。(他にも様々な構成要素が有るが今回の説明に必要な分だけ列挙してある。)
-
Prolog
クラス: Prologの実行エンジン -
PrologControl
クラス: Prologの実行エンジンを制御するコントローラ -
Predicate
クラス: Prologの述語を表現する抽象クラス - (参考)
Term
クラス: Prologの項を表現する抽象クラス
PrologCafeは、並行処理をサポートしており、1つのJVMプロセスで複数のPrologの実行エンジンをマルチスレッドで動作させることができる。例えば、PrologControl (Prolog Cafe v1.2 API Specification)のコーディング例では、複数のPrologControl
インスタンスを生成し、複数のPrologの実行エンジン(Prolog
インスタンス)をそれぞれ異なるスレッドでラップして制御している。
しかし、 PrologControl
インスタンスは、内部でスレッドを必ず生成するため、スレッドを生成せずにPrologの実行エンジンを使いたい場合に勝手が良くない。エンジンを生成する度にスレッドも生成されては困る場合もあるだろう。
そこで、本稿では スレッドを生成せずに Prologの実行エンジンを制御するコントローラの作成方法を記録する。なお、本稿で扱うPrologCafeのバージョンは1.2.5
とする。
設計
方針
PrologControl
インスタンスは、Prolog
インスタンスの制御だけでなく、ハンドラの役割も持っている。例えば、Prolog
インスタンスは、自身(実行エンジン)がsuccess
あるいはfail
した場合、PrologControl#success
やPrologControl#fail
を呼ぶことで、PrologControl
に成功または失敗したことを通知している。
従って、Prolog
はPrologControl
と相互に依存しており、言い換えれば相互に参照している。このため、Prolog
インスタンスは単体で動作させることができず、PrologControl
のインスタンスも動作に必須となっている。
設計方針として、これらの相互依存を断ち切る方法もあるが、大掛かりな変更を要する(動かなくなるサンプルが多量に出てくると思われる)ため、今回は元のクラスの変更は控えて継承を用いる方針とする。
手順
大まかな手順は以下のとおり。要するに、スレッド関連のメソッドを全て潰して、状態モデルとAPIを再定義する。
-
PrologControl
を継承する。 -
PrologControl
の全てのメソッドをオーバーライドする。 -
PrologControl
のスレッド制御に関する全てのメソッドでUnsupportedOperationException
をスローさせる。 - スレッドの状態遷移モデルをベースに設計されているので、スレッドを使わない状態遷移モデルを新たに定義する。
- 実行結果の表現の設計が甘いので再定義する。
- ちなみに、あんまり好みじゃないメソッドもついでに
UnsupportedOperationException
をスローさせている。
実装
こんな感じ。
package maglog.prolog;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_call_1;
import jp.ac.kobe_u.cs.prolog.lang.Predicate;
import jp.ac.kobe_u.cs.prolog.lang.Prolog;
import jp.ac.kobe_u.cs.prolog.lang.PrologControl;
import jp.ac.kobe_u.cs.prolog.lang.PrologException;
import jp.ac.kobe_u.cs.prolog.lang.Success;
import jp.ac.kobe_u.cs.prolog.lang.SystemException;
import jp.ac.kobe_u.cs.prolog.lang.Term;
/**
* スレッドレス版の実行エンジンのコントローラ
*
* @author Masayuki Higashino
*/
public class PrologContext extends PrologControl {
/**
* 実行エンジンの状態
*
* @author Masayuki Higashino
*/
public enum State {
NEW, RUNNABLE, STOPPED
}
/**
* 実行エンジンの処理結果
*
* @author Masayuki Higashino
*/
public enum Result {
SUCCESS, FAILURE
}
private static final long serialVersionUID = -327097420774349657L;
private Prolog engine;
private Predicate code;
private State state;
private Result result;
public PrologContext() {
engine = new Prolog(this);
code = null;
state = State.NEW;
result = null;
}
public Prolog getEngine() {
return engine;
}
public Predicate getCode() {
return code;
}
public State getState() {
return state;
}
public Result getResult() {
return result;
}
public boolean isStopped() {
return state == State.STOPPED;
}
public void init() {
engine.init();
}
public void setPredicate(Predicate code) {
this.code = code;
}
@Override
public void setPredicate(Term code) {
this.code = new PRED_call_1();
this.code.setArgument(new Term[] { engine.copy(code) }, new Success(this));
}
public Result execute() throws PrologException {
state = State.RUNNABLE;
main_loop: while (true) {
while (engine.exceptionRaised == 0) {
if (state != State.RUNNABLE)
break main_loop;
code = code.exec(engine);
}
switch (engine.exceptionRaised) {
case 1: // halt/0
break main_loop;
case 2: // freeze/2
throw new SystemException("freeze/2 is not supported yet");
default:
throw new SystemException("Invalid value of exceptionRaised");
}
}
return result;
}
// マーカーアノテーションを使ってみたかっただけ。無くてもよし。
@Target(ElementType.METHOD)
private @interface Handler {
}
@Handler
@Override
protected void success() {
state = State.STOPPED;
result = Result.SUCCESS;
}
@Handler
@Override
protected void fail() {
state = State.STOPPED;
result = Result.FAILURE;
}
@Override
public void start() {
throw new UnsupportedOperationException();
}
@Override
public void stop() {
throw new UnsupportedOperationException();
}
@Override
public void join() {
throw new UnsupportedOperationException();
}
@Override
public boolean ready() {
throw new UnsupportedOperationException();
}
@Override
public void run() {
throw new UnsupportedOperationException();
}
@Override
public void cont() {
throw new UnsupportedOperationException();
}
@Override
public boolean next() {
throw new UnsupportedOperationException();
}
@Override
public boolean execute(Predicate p, Term[] args) {
throw new UnsupportedOperationException();
}
@Override
public boolean call() {
throw new UnsupportedOperationException();
}
@Override
public boolean redo() {
throw new UnsupportedOperationException();
}
@Override
public boolean in_success() {
throw new UnsupportedOperationException();
}
@Override
public boolean in_failure() {
throw new UnsupportedOperationException();
}
@Override
public void setPredicate(Predicate code, Term[] args) {
throw new UnsupportedOperationException();
}
}
実験
Java言語でProlog言語のhello, world
を書いてみる。
package maglog;
import java.io.PrintWriter;
import maglog.prolog.PrologContext;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_flush_output_0;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_nl_0;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_write_1;
import jp.ac.kobe_u.cs.prolog.lang.Predicate;
import jp.ac.kobe_u.cs.prolog.lang.Success;
import jp.ac.kobe_u.cs.prolog.lang.SymbolTerm;
import jp.ac.kobe_u.cs.prolog.lang.Term;
public class Example {
public static void main(String[] args) {
PrologContext context = new PrologContext();
context.init();
context.getEngine().setCurrentOutput(new PrintWriter(System.out));
Predicate p1 = new Success(context);
Predicate p2 = new PRED_flush_output_0();
p2.setArgument(new Term[] {}, p1);
Predicate p3 = new PRED_nl_0();
p3.setArgument(new Term[] {}, p2);
Predicate p4 = new PRED_write_1();
p4.setArgument(new Term[] { SymbolTerm.makeSymbol("hello, world") }, p3);
context.setPredicate(p4);
while (!context.isStopped())
context.execute();
}
}
hello, world
おわりに
ニッチすぎてストックされる気がしない。