付録: GNU Prolog for Java の Android への移植

2012.3.28 (鈴)

1. はじめに

本編 では他の Android プログラムに応用できる土台として一組の完結したユーザ・インタフェースを提示した。 ここでは,全く独立に作られた既存の言語処理系にそれを適用することで,その再利用可能性を実証する。

ここで Android に移植する処理系として GNU Prolog for Java [gnu.org] (以下,GPJ) を使う。GPJ は ISO Prolog に準拠した Java 上の Prolog インタープリタであり, GNU Lesser General Public License (Version 3 またはそれ以降) で公開されている。

ISO Prolog によるプログラミングについては,例えば W. F. Clocksim & C. S. Mellish: "Programming in Prolog: using the ISO standard", 5th ed., Springer-Verlag, 2003, ISBN 3-540-00678-8 を参考にされたい。

2. 移植の概要

GPJ はクラスライブラリとして Java アプリケーションに組み込み,知識情報処理エンジンとして使うように作られており, それ自体では対話的な Prolog インタープリタとして使うことはできない。 そこでまず Prolog の対話シェルを通常の端末入出力を使って作成した。

次に,Android 上に java.io.Writer と1行入力処理が OutputWriter.javaInteractiveInput.java として既にあるから,これらを前提として具体化できるように定義した抽象化コンソールを AbstractConsole.java として作成した。

そして,この抽象化コンソールを利用するように対話シェルを書き直した。 これが現在の Prolog.java である。 その main メソッドでは,通常の端末入出力を使って仮に AbstractConsole を具体化しており,通常の端末上で単体でテスト動作させることができる。 下記に Prolog#main(String[]) を示す。

    /** 通常のコンソール上で動作する Prolog インタープリタ。
     * コマンド行引数を一つとって Prolog として解釈する。
     * EOF 文字が打鍵されるまで問い合わせを入力して解を出力する。
     */
    public static void main(String[] args) {
        AbstractConsole con = new AbstractConsole () {
                java.io.Console con = System.console();
                @Override public String readLine(String prompt) {
                    return con.readLine(prompt);
                }
                @Override public PrintWriter writer() {
                    return con.writer();
                }
            };
        if (args.length != 1) {
            con.printf("Usage: java gnu.prolog.cui.Prolog foo.prolog\n");
            System.exit(1);
        }
        Prolog interp = new Prolog (con, args[0]);
        interp.run();
    }

この対話シェルは,ルール集である Prolog スクリプトを最初に読み込み, それ以降,そのルールに基づいた問い合わせに対話的に答える,という動作をとる。 これを Android アプリケーションとして素直に実現した場合,Prolog スクリプトを選択する動作が最初になる。 したがって FinderActivity.java を主画面とすることにし,AndroidManifest.xml の記述と各アクティビティでの遷移処理を書き換えてこれを実現した。

Prolog 対話シェルを動作させる InterpreterActivity.javaonCreate メソッドでは,次のようにして抽象化コンソールを具体化して対話シェルに与える。 前述の main メソッドと比較されたい。

        final PrintWriter writer = new PrintWriter
            (new OutputWriter (textView));
        PrintWriter promptWriter = new PrintWriter
            (new EmphasizedOutputWriter (textView));
        input = new InteractiveInput (writer, promptWriter);
        AbstractConsole con = new AbstractConsole () {
                { defaultPrompt = "_"; }
                @Override public String readLine(String prompt) {
                    return input.readLine(prompt);
                }
                @Override public PrintWriter writer() {
                    return writer;
                }
            };
        reader = con.reader();

        // Intent で指定されたファイルを Prolog スクリプトとして読み込む
        currentPath = getIntent().getStringExtra(Common.FILE_PATH);
        setTitle(currentPath);
        Prolog interp = new Prolog (con, currentPath);
        interpThread = new Thread (null, interp, "Interp",  1000000);
        interpThread.setDaemon(true);

InterpreterActivity が消滅するときは,readLine(prompt) メソッドから AbstractConsole が仮想的に構築している Reader を陽に close() し, InteractiveInput 内で wait 待ちしている場合のために interrupt() することで,可能な限り穏やかに対話シェルを終わらせる。 このとき対話シェルはその仮想的な端末入力から EOF (End Of File) を受け取ることになる。

    /** 終了時,インタープリタのスレッドをなるべく穏やかに終わらせる。
     */
    @Override protected void onDestroy() {
        try {
            reader.close();
        } catch (IOException ex) { // ここには来ない cf. AbstractConsole
            throw new RuntimeException (ex);
        }
        interpThread.interrupt();
        super.onDestroy();
    }

3. 操作の概要

操作の起点が Finder であることを除き本編 Semi-Arc on Android 0.3 の操作方法と基本的に同じだから,適宜省略して操作方法を説明する。

ファイル tmp/depth_search.prolog を選択し,メニューから Edit を選ぶと,ファイルの文字数をトーストで表示して Editor が起動する。


なお,ここでは Prolog スクリプトとして M. Hiroi 氏による 経路の探索(1) - M.Hiroi's Home Page / Prolog Programming [geocities.jp] にある修正版の例題を使っています。 ただし,ISO Prolog にあわせて not\+ に置き換え,足りない述語 reverse を補っています。

MENU ボタンを押して出現するメニューから Exit & Load を選ぶと,Prolog Interpreter が起動する。

depth_search(a, k, [], Path). と入力する。 ak が小文字であり, Path の先頭が大文字であることに注意されたい。 Prolog は定数としてのシンボルと変数を先頭文字の大小で区別する。

Enter キーによって入力行が Prolog インタープリタに伝えられる。 1行おいて与えられたルールを満たす (この場合は a 地点から k 地点への経路となる) 変数 Path の値が一つ表示される。

ここでセミコロンを入力して Enter キーを押すと,別の解が表示される。

さらにまたセミコロンを入力して Enter キーを押すと,さらに別の解が表示される。

Editor からはいつでも「戻る」ボタンで Finder に戻ることができる。 また MENU ボタンから Editor に遷移することができる。

4. アクティビティ間の遷移

最初にスクリプト・ファイルの選択が必須であることから, アクティビティ間の遷移は Semi-Arc on Android 0.3 の遷移方法と比べ簡素になった。 結果を待ち受けることなく,単純にファイルのパス名の文字列を与えて他のアクティビティに遷移するだけである。

EditorActivity 処理からの遷移はその onOptionsItemSelected メソッドの下記の処理で実現した。

        } else if (item == exitItem) {
            Intent intent = new Intent(this, InterpreterActivity.class)
                .putExtra(Common.FILE_PATH, path);
            startActivity(intent);
            finish();
            return true;
        }

InterpreterActivity からの遷移はその onOptionsItemSelected メソッドの下記の処理で実現した。

        if (item == editItem) {
            Intent intent = new Intent (this, EditorActivity.class)
                .putExtra(Common.FILE_PATH, currentPath);
            startActivity(intent);
            finish();
            return true;
        } else if (item == clsItem) {

両アクティビティがそれぞれ相手を startActivity し,自分を finish することで相互に遷移しあう。 ここでアプリケーションの起点となる FinderActivity が遷移先としては陽に現れないことに注意されたい。

FinderActivity からの遷移はその onClick メソッドの下記の処理で実現した。

        if (item == 0) {        // Edit
            Intent intent = new Intent (this, EditorActivity.class)
                .putExtra(Common.FILE_PATH, path);
            startActivity(intent);
        } else if (item == 1) { // Load
            Intent intent = new Intent (this, InterpreterActivity.class)
                .putExtra(Common.FILE_PATH, path);
            startActivity(intent);
        } else {                // Remove

ここでは選択された項目に応じて EditorActivity または InterpreterActivitystartActivity するが,自分に対して finish はしない。 この場合,EditorActivityInterpreterActivity のどちらからでも「戻る」ボタンでただちに FinderActivity に戻ることになる。

5. おわりに

ここでは GNU Prolog for Java の Android への移植を通して Android プログラムの再利用の実例を示した。 プログラム全体としてのライセンスの取り扱いの簡素化や,ソース公開プログラムとしての立場の明確化のため, このバージョンのユーザ・インタフェース・プログラムについて GNU Prolog for Java 本体と同じく GNU Lesser General Public License (Version 3 またはそれ以降) のもとに置く。

なお,自前の Prolog 処理系 Tiny Prolog in Java もあるが,Prolog としては小さなサブセットにすぎず,またいわば自作自演となり,再利用可能性の実証として説得力に欠けるため, ここでは使わなかった。その Android への移植は読者への課題とする。


総目次へ


Copyright (c) 2012 OKI Software Co., Ltd.