はじめに
NLM100本ノックに挑戦していて課題ではないのですが、出力結果をJSONまたはDBに保存したいと思いその方法を調べていました。
その時の備忘録として外部ファイルとDBの入出力サンプルを投稿します。
外部ファイルについてはScheme(スキーマ)系とJS系の入出力処理が使えます。
- Scheme(スキーマ)のポート系
open-output-file / open-input-file
call-with-output-file / call-with-input-file - JS(Node.js)のfs系
fs.writeFileSync/fs.readFileSync
DBについてはJSで使えるものであれば何でも使えますがNode.jsのsqliteを取り上げます。
参考
https://lips.js.org/docs/scheme-intro/input-output#writing-to-a-file
https://lips.js.org/docs/lips/intro#nodejs
https://nodejs.org/api/sqlite.html
環境
OS: windows11-wsl(ubuntu-24.04)
LIPS: 1.0.0-beta.21 (2025-12-27)
Node.js: v24.21.0.
0. その前に
私はLIPSでJS関数を利用するとき、JSでサンプルコードを書いて動作確認することがあります。
それからLIPSコードにJSコードをコピペして編集して使います。
そのとき、パラメータ区切りやデータ構造内の","を除去し忘れることがよくあります。
そしてエラーにはまるのです。次のようのメッセージです。
You can't call `unquote` outside of quasiquote at line ..
自分なりにあたりをつけて調べたのですが時間の無駄と判断しclaude先生に伺ってみました。
私の質問:「原因はデータの記法ですか?」
claude先生のお答え:「原因はJSONの中身ではなく、プログラム側の catch 節の書き方です。」
一発的中でした。(;´д`)ハズカシ
その時の原因箇所:
(catch (err) (console.error "例外をキャッチしました:", err.message))
JSコードをコピペして上のようなエラーに出くわしたらカンマの削除漏れを疑いましょう。
1. Scheme(スキーマ)のポート系
まずはオーソドックスなScheme関数を見てみましょう。
1.1 open-output-fileによる出力
単純に1行の文字列("Hello, Scheme!")を出力するだけです。
※既存ファイルに対する書き込みはエラーになります。ファイル更新は後述のfs系関数を使います。
(define (write-test fname messsage)
(let (
;; 出力用ポートを開く
(p (open-output-file fname))
)
;; 文字列を書き込む(display はクォートなし、write はクォート付きで出力)
(display messsage p)
(newline p)
;; (write messsage p) ;Wクォーテーションと一緒に出力される。
;; ポートを閉じる
(close-output-port p)
)
)
(write-test "sample.txt" "Hello, Scheme!")
出力関数にはwriteもあります。
1.2 open-input-fileによる出力
上で作ったファイルを読み込んでみましょう。
(define (read-test fname)
(let (
;; 出力用ポートを開く
(p (open-input-file fname))
)
(console.log (read-line p)) ;
;; (console.log (read-string 20 p))
;; ポートを閉じる(書き込みを反映させるため必須)
(close-input-port p)
)
)
(read-test "sample.txt")
入力にはread-stringもあります。読み込み文字数を指定しなければいけません。
1.3 call-with-output-fileによる出力
call-with-output-fileは出力ポートを開けてそこに出力することはopen-output-fileと同じですが、
ポートcloseを明示的にコールする必要がないというところがポイントです。
※既存ファイルに対する書き込みはエラーになります。ファイル更新は後述のfs系関数が簡単です。
(define (write-test2 fname message-str)
(call-with-output-file fname
(lambda (p)
(display message-str p)
(newline p)
)
)
)
(write-test2 "sample.txt" "Hello, Scheme!")
1.4 call-with-input-fileによる入力
call-with-input-fileは入力ポートを開けてそこから入力することはopen-input-file同じですが、
ポートをcloseする必要がないというところがポイントです。
(define (read-test2 fname)
(call-with-input-file fname
(lambda (p)
(display (read-line p))
(newline)
)
)
)
(read-test2 "sample.txt")
2. JS(Node.js)のfs系
2.1 fs.writeFileSyncによる出力
writeFileSyncを使ってオブジェクトデータ(JSON)を書き出してみましょう。
;; Node.js の fs モジュールを読み込む
(define fs (require "fs"))
(define (writetest3)
(let* (
;; 1. 書き込みたいデータ(Schemeのオブジェクト / 連想リストなど)
(my-data `&(:name "山田 太郎"
:job "シルバー人材派遣"
:condition #("高血圧" "変形性膝関節炎")))
;; 2. JSON.stringify を使ってインデント付きのJSON文字列に変換
(json-string (JSON.stringify my-data))
)
;; 3. fs.writeFileSync で同期的にファイルへ新規作成・上書き保存
(fs.writeFileSync "output.json" json-string "utf8")
)
)
(writetest3)
2.2 fs.readFileSyncによる入力
readFileSyncを使ってオブジェクトデータ(JSON)を読み込んでみましょう。
(define (readtest3)
(call-with-input-file "output.json"
(lambda (p)
;;1行分のデータを読み込んでJSONデータを解析する。
(let ((obj (JSON.parse (read-line p))))
(print obj.name)
(print obj.job)
;; (print obj.condition)
(vector-for-each (lambda (e) (display e) (display " ")) obj.condition)
(newline)
)
)
)
)
(readtest3)
3 応用 (NLM100本ノックの3章-29の保存・読み込み)
NLM100本ノックの3章で取り組んだ課題No28の結果を保存して取りだす処理を作ってみましょう。
(これが今回の投稿の動機となった調べ事です。 v( ̄Д ̄)v イエイ)
3.1 No28 結果保存
No28の課題を処理して結果をJSONファイルとして保存します。
ここで使う課題処理の関数は以下の5個です。
- 問題20: load-uk-article - JSON形式のMediaWikiテキスト読み込み
- 問題25: dic-obj - テンプレート(フィールド名:値形式の基礎情報)抽出
- 問題26: remove-emphasis - MediaWikiの強調マークアップ除去
- 問題27: remove-internal-links - MediaWikiの内部リンクマークアップを除去
- 問題28: remove-mediawiki-markups - その他MediaWikiマークアップを除去
課題処理の詳細はLIPS Schemeを使ってみました11を参照してください。
(load "./nlp100_ch3.scm") ;外部コードをロード
;; 課題No28の処理
(define (test-no28b)
(let* (
(file-path "./jawiki-country/jawiki-country.json.gz")
(article-text (await (load-uk-article file-path)))
)
(if article-text
(let* (
(result-no25 (dic-obj article-text)) ;問題25の結果
(result-no26 (remove-emphasis result-no25)) ;問題26の結果
(result-no27 (remove-internal-links result-no26)) ;;問題27の結果
)
(remove-mediawiki-markups result-no27) ;問題28の結果
)
#f ; wekipediaテキスト読み込み失敗
)
)
)
;; NLP100 3章28の課題を実行して結果をalist形式で返す
(define (save-result-no28)
(let ((result-no28 (test-no28b)))
;;(result-no28がnullでなければ保存処理を行う。
(if result-no28
(let* (
;; alist を JavaScript のオブジェクトに変換
(js-object (alist->object result-no28))
;; JSON文字列に変換(スペース2つで綺麗にインデント整形)
;; (json-string (JSON.stringify js-object #f 2))
(json-string (JSON.stringify js-object))
)
;; ファイルを同期的に新規作成・上書き保存
(fs.writeFileSync "test28.json" json-string "utf8")
)
(console.log "処理できませんでした。")
)
)
)
(save-result-no28)
3.2 No28 結果読み込み
(define (load-result-no28)
(try
(let* (
(json-data (fs.readFileSync "./test28.json" "utf-8"))
(data (JSON.parse json-data))
)
;; (print (object->alist data))
(for-each
(lambda (pair) (print pair))
(object->alist data)
)
)
(catch (err) (console.error "例外をキャッチしました:", err.message))
)
)
(load-result-no28)
4 sqllite
最後はsqliteです。JSでは定番のDBモジュールのようです。
;; 1. node:sqlite モジュールを読み込む
(define sqlite (require "node:sqlite"))
;; 2. データベースのインスタンスを作成 (同期版の DatabaseSync を使用)
(define db (new sqlite.DatabaseSync ":memory:")) ;メモリ保管
;; (define db (new sqlite.DatabaseSync "./sqtest.db")) ;ファイル保存
(let (
(stmt_create_table (string-append
"CREATE TABLE users "
"(id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL);"))
)
;; 3. テーブル作成 (exec メソッドを実行)
(--> db (exec stmt_create_table))
;; 4. データ挿入 (プリペアドステートメントの作成と実行)
(let* (
(stmt_insert "INSERT INTO users (name) VALUES (?);")
(insert-username (--> db (prepare stmt_insert)))
)
(--> insert-username (run "ほげ"))
(--> insert-username (run "ふが"))
)
;; 5. データ取得 (作成したプリペアドステートメントから全件取得)
(let* (
(stmt_selectall "SELECT * FROM users;")
(select-stmt (--> db (prepare stmt_selectall)))
)
;; 6. 結果をコンソール出力
(vector-for-each
(lambda(e) (console.log e.id e.name))
(--> select-stmt (all))
)
)
)
おわりに
私のAI利用は無料枠で使っている程度なのですが、今回もAIテクノロジーの進化を感じました。
1年前はLIPS関係ではAI側の反応に学習不足を感じさせられることがありました。
「(((ノ´ー`)ノ マ-ダマダ」という感じでした。
今ではGaucheなど他のschemeとのごちゃ混ぜ回答がなくなり、逆に自分の学習不足を感じさせられるほどです。
これからもAI先生を頼りにしながらプログラミング学習を進めようと思います。
