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を使ってみました16(ファイル/sqllite入出力)

0
Posted at

はじめに

post16.jpg

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. その前に :bee:

私は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先生を頼りにしながらプログラミング学習を進めようと思います。

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?