0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LIPS Schemeを使ってみました5(自然言語処理)

Posted at

はじめに

前回(「LIPS Schemeを使ってみました4(DOM操作)」に引き続きLips使ってみましたの記事です。

cow25.png

JavaScript拡張ライブラリの利用を今回のテーマにしました。
自然言語処理はLIPS系言語が得意とする分野ということでJavaScriptから手軽に使えるkuromojiを試しました。

kuromojiをlipsで使うにあたって、次の3点が実装のポイントとなりました。

  • JavaScript関数にLips関数をコールバックとして渡す
  • JavaScript関数にオブジェクトデータ(構造体データ)を渡す
  • JavaScript関数をLipsコード中で動的に評価して実行する

環境

kuromojiライブラリ:
githubからlibディレクトリ一式とbuild/kuromoji.jsを入手し、HTML格納フォルダーから相対パスで指定できるところに配置します。

Webサーバー:
kuromojiライブラリを外部ファイルとしてロードするためWebサーバーを立ち上げる必要があります。
私はVS Codeから手軽に使えるLive Serverをインストールしました。

ちなみにkuromojiライブラリと辞書はjsDelivrのCDNパスで指定することもできます。

CDNパスのライブラリを使う場合:

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/build/kuromoji.js"></script>

CDNパスの辞書を使う場合:

(kuromoji.builder '&(:dicPath "https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/") )

ファイルの配置例
deploy.png

※その他
LISPはカッコ括りによるスコープの判別が大変です。
私はVS codeを使っているので下記の拡張ツールをインストールしました。
vscode-scheme
vscode-ol-syntax
LIPS(Scheme一般?)ではスコープ設定の可読性を向上させるため、()以外に[]も使えます。今回気づきました。

実装ポイントの概要

実装ポイントになった項目を説明代わりにJavaScriptとLipsの対訳コードによるシンプルサンプルを以下に示します。

A. JavaScript関数にLips関数をコールバックとして渡す

kuromojiの初期処理では非同期で動作しますのでオブジェクト取得するためにコールバックが必要になります。

<script type="text/javascript"> 
    // JavaScriptの例 .. コールバック引数を呼ぶ関数
    function j_launcher(fnc) { fnc("(`ハ´)アイヤー"); }
    /* JavaScriptの例 .. パラメータの文字列をログ出力する無名関数を渡す */
    j_launcher((msg) => console.log(msg));
</script>
<script type="text/x-scheme" bootstrap>
    ;; Lipsの例 .. パラメータの文字列をログ出力する無名関数を渡す
    (j_launcher (lambda (str) (console.log str)))
</script>

B. JavaScript関数にオブジェクトデータ(構造体データ)を渡す

kuromojiに辞書のパスをセットする際の技になります。
LIPSのREPLサイトのトップのサンプルにquasiquoteという「`&」を使ってオブジェクトをJavaScript関数(JSON.stringify)に渡しているサンプルがありますが、下はそれをさらに簡単にした例です。

<script type="text/javascript">
// JavaScriptの例 -- オブジェクト(obj)を受け取ってログ出力する
function j_testfnc(obj) { console.log(obj); }
// 呼び出し例
j_testfnc({item1:"abc", item2:123})
</script>
<script type="text/x-scheme" bootstrap>
;;LipsからJavaScript関数を呼び出す例 - オブジェクトデータ(obj)をJavaScript関数(j_testfnc)に渡す
(let ((obj `&(:item1 "def":item2 ,456)))
    (j_testfnc obj)
)
</script>

C. JavaScript関数をLipsコード中で動的に評価して実行する

既存のJavaScript機能がLipsでどうしても動作させられないケースに出くわしたときなどに有効な技です。

;;ローカル変数(fn)にJavaScriptの無名関数の評価済オブジェクトを保管して実行する
(let [(fn (self.eval "(function(name) {return 'Hello ' + name})"))] 
  (print (fn "二郎"))) ; => Hello 二郎

※この機能はgithubのread.mdに紹介がありますが、Documentには説明がありません。(2025/10/10現在)

サンプルのコード

では準備が整ったの思いますので、以下にkuromojiをLipsで使うをサンプルを見ていきましょう。
尚、今回もコード内容についてはJavaScriptの対訳コードをもって説明に代えさせていただきます。
その方が直感的に理解しやすいかと思いました。
( `・∀・´)ノヨロシク

0. サンプルの起動画面

下のHTMLはkuromoji処理呼び出しと結果表示を行う画面定義です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- LIPSとkuromojo処理系へのURL -->
    <script src="https://cdn.jsdelivr.net/npm/lips@beta/dist/lips.min.js"></script>
    <script type="text/javascript" src="./lib/kuromoji.js"></script>
    <title>lipsお試し 5つめ</title>
</head>
<body>
<h2>日本語形態素解析</h2>
<div >
  <p>日本語の文を入力してください。:</p>
  <p>
    <input type="text" id="input_text" value="太郎は花子に本を渡した。" style="width: 300px;" />
  </p>
  <p style="text-align: right; width: 300px;">
    <input type="button" id="btn_test1" value="解析" />
    <input type="button" id="btn_test2" value="クリア" />
  </p>
</div>
<hr>
  <ol id="elements">
    <!-- ここに形態素解析の結果をリスト表示します。-->
  </ol>
  <script type="text/x-scheme" src="lips_test5.scm"  bootstrap></script>
  <script type="text/x-scheme" bootstrap>
    (initKuromoji) ; 初期化(kuromoji辞書パス指定ボタンイベント登録)
  </script>
</body>
</html>

下は完成コードのスクショです。テキストボックスに任意の日本語をタイプして解析ボタンをクリックしたら画面下部に語単位の形態素情報を表示する。クリアボタンで表示内容をクリアする。というだけの内容です。
scrn_shot.png

1. 初期化

Lipsコード

(define g_tokenizer )  ; 解析器オブジェクト保管変数 
(define (initKuromoji)
    ;; ビルダー取得 
    (let ((builder (kuromoji.builder '&(:dicPath "./lib/dict") )) )  
        ;; 解析器オブジェクト取得
        (builder.build  (lambda (err, tokenizer) (set! g_tokenizer tokenizer))) 
    )
    ;; 解析ボタンイベント関数登録
    (let ((button1 (document.getElementById "btn_test1")))
        (set! button1.onclick (lambda () (clickhandle)))
    )
    ;; 結果クリアボタンイベント関数登録
    (let ((button2 (document.getElementById "btn_test2")))
        (set! button2.onclick (lambda () (clearResult)))
    )
) ; -- end of initKuromoji --

JavaScriptコード

let g_tokenizer = null; // 解析器オブジェクト保管変数 
function initKuromoji() {
    const builder = kuromoji.builder({ dicPath: "./lib/dict/" });
    builder.build(function (err, tokenizer) { g_tokenizer = tokenizer; });
    const button1 = document.getElementById("btn_test1");
    button1.onclick = clickhandle;
    const button2 = document.getElementById("btn_test2");
    button2.onclick = clearResult;
}

2. クリックイベントハンドラー

Lipsコード

;;; 解析ボタンクリックイベントを受けて形態素解析解析を行う
(define (clickhandle) 
    (if (null? g_tokenizer) 
        (alert "KUROMOJIの初期処理が完了していません。") 
        (let ((element (document.getElementById "input_text")))
            ;(console.log g_tokenizer)
            (analyze element.value) ; 形態素解析解析
        )
    )
)
;;; クリアボタンイベントを受けて表示内容をクリアする
(define (clearResult) 
   (let ((fn (self.eval "(function() {
            let list = document.getElementById('elements');
            list.innerHTML = '';
            })"
        ))) 
        (fn))
)

clearResultはolタグ下の要素をクリアする処理ですが、まんまJavaScriptを実行しています。当初は(set! list.innerHTML "")というコードにしていましたが、"Cannot assign ..."というエラーが発生し、回避手段としてJavaScriptコードを使いました。set!と同じことなのですが、原因は不明です。set!でDOMオブジェクトの属性を一度セットするとReadOnlyになっているような感じです。

JavaScriptコード

function clickhandle() {
    if (g_tokenizer == null) {
        alert("KUROMOJIの初期処理が完了していません。")
    } else {
        element = document.getElementById("input_text");
        analyze(element.value)
    }
}
function clearResult() {
    const list = document.getElementById('elements');
    list.innerHTML = '';
}

3. 形態素解析 & 画面表示

Lipsコード

;;;グローバル変数(g_tokenizer)に保管したkuromojiオブジェクトの解析器を使って文(str)を形態素解析する。
(define (analyze str) 
    (let ((tokens (g_tokenizer.tokenize str)) )
        (showElements tokens)
    )
)
;;;文を構成する各語の形態素情報(vecTokens)をリスト表示する。
(define (showElements vecTokens) 
    (let ((list (document.getElementById "elements")))
        (vector-for-each [lambda(token) 
            (let [(li (document.createElement "li"))
                    (elmnt (format 
                        "表層形<surface_form>:~a, 品詞<pos>:~a, 品詞細分類1<pos_detail_1>:~a" 
                        token.surface_form token.pos token.pos_detail_1)
                    )
                ]
                (set! li.textContent elmnt)
                (list.appendChild li)
            )
            ] vecTokens ; end of lambda(token)
        ) ; end of ector-for-each
    )
)

JavaScriptコード

function analyze(str) {
    const tokens = g_tokenizer.tokenize(str);
    showElements(tokens);
}

function showElements(vecTokens) {
    const list = document.getElementById("elements");
    vecTokens.forEach((token) => {
        console.log(token)
        const li = document.createElement("li")
        const elmnt = "表層形<surface_form>:" + token.surface_form + 
        ", 品詞<pos>:" + token.pos + 
        ", 品詞細分類1<pos_detail_1>:" + token.pos_detail_1;
        li.textContent = elmnt;
        list.appendChild(li);
    });
}

参考

おわりに

これまでLIPSを使ってみての感想ですが、LIPSがJavaScriptで書かれているので当然でしょうがJavaScriptで表現できる処理はすべてLIPSでもできます。
JavaScriptもある意味関数志向的言語であり両言語は親和性が高いため、JavaScriptユーザーにはLISP学習のハードルを下げてくれる言語だと思いました。
既存のJavaScriptプログラムを分割しながら(または切り取りながら)評価を進めることができるのでLipsはお薦めしたい処理系であると感じました。
でも、産業用プログラミング言語としてLISPが普及する見込みは低いでしょうね。
ここまで本稿を見てくださり、「ありがた山」でございます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?