Posted 2021-10-14 03:53:50 GMT
前回は、Allegro Prolog + AllegroCache の組み合わせを試しましたが、今回は、同じく商用の処理系であるLispWorksのCommon Prolog + Common SQLの組み合わせで試してみたいと思います。
Common Prologは、LispWorksの組み込みProlog、Common SQLは、各種SQLとの接続パッケージでSQLのテーブルとオブジェクトとのマッピングが可能な所謂ORM機能もあります。
これらは元々KnowledgeWorksとして1990年からあるようですが、Common SQLは1992年にDataWorksの後継として開発されたようなので若干新しいようです(といっても古い)
パッケージ定義/ユーティリティ等は前回のものを引き続き利用しています。
(require "sql")
(require "sqlite")
(require "kw")(defpackage covid19.mhlw.go.jp
(:use cl fare-csv sql kw))
(in-package covid19.mhlw.go.jp)
Allegro CLのAllegroCacheに対応するものとしてCommon SQLとSQLite3を試してみます。
Common SQLではSQLをLisp的に記述することが可能ですが、そのためにはリーダーマクロを有効する必要があります。
便利なようなそうでもないような。
SQLのクエリを文字列で与えるquery
という仕組みもありSQLに慣れた人はそちらの方が便利かもしれません。
(locally-enable-sql-reader-syntax)
;; SQLite3のDBへ接続(DBファイルが作成される)
(connect "covid19.sqlite3");; テーブル作成
(create-table [newly_confirmed_cases_daily]
'(([date] text)
([prefecture] text)
([newly_confirmed_cases] integer)))
;;query版
(query
"create table newly_confirmed_cases_daily(date text, prefecture text, newly_confirmed_cases integer);")
;; データベースへ登録
(with-transaction
(dolist (line (subseq (cdr *newly_confirmed_cases_daily.csv*) 0 nil))
(destructuring-bind ($date $prefecture $newly-confirmed-cases)
line
(insert-records :into [newly_confirmed_cases_daily]
:attributes '([date] [prefecture] [newly_confirmed_cases])
:values (list (parse-date $date)
$prefecture
$newly-confirmed-cases)))))
;;query版
(with-transaction
(dolist (line (subseq (cdr *newly_confirmed_cases_daily.csv*) 0 nil))
(destructuring-bind ($date $prefecture $newly-confirmed-cases)
line
(query
(format nil
"insert into newly_confirmed_cases_daily(date, prefecture, newly_confirmed_cases) values ('~A', '~A', ~A);~%"
(parse-date $date)
$prefecture
$newly-confirmed-cases)))))
;;;愛知の2021年9月の合計
(select [sum [newly_confirmed_cases]] :from [newly_confirmed_cases_daily]
:where [and [= [prefecture] "Aichi"]
[between [date] "2021-09-01" "2021-09-30"]]
:flatp T)
→ (21209)
("SUM(NEWLY_CONFIRMED_CASES)") ;;query版
(query "select sum(newly_confirmed_cases) from newly_confirmed_cases_daily where prefecture='Aichi' and date between '2021-09-01' and '2021-09-30'" :flatp T)
;;;都道府県のリストを取得する
(select [distinct [prefecture]] :from [newly_confirmed_cases_daily]
:where [<> [prefecture] "ALL"])
;;query版
(query "select distinct prefecture from newly_confirmed_cases_daily where prefecture != 'ALL'"
:flatp T)
;;;都道府県ごとの合計
(select [prefecture] [sum [newly_confirmed_cases]] :from [newly_confirmed_cases_daily]
:where [<> [prefecture] "ALL"]
:group-by [prefecture]
:limit 2)
→ (("Shizuoka" 26308) ("Shiga" 12388))
;;query版
(query "
select prefecture, sum(newly_confirmed_cases) from newly_confirmed_cases_daily
where prefecture != 'ALL'
group by prefecture
limit 2")
さて課題自体は、Prologを使わずにSQLで解決してしまいましたが、Prolog連携も試してみます。
SQLのテーブルとのマッピングにはdef-view-class
という、standard-db-class
をメタクラスとしたdefclass
の派生を利用します。
SQL=Objectと、Prologとの連携ですが、KnowledgeWorksではOPS5互換の前向き推論とProlog互換の後ろ向き推論をワーキングメモリ上のオブジェクトと連携させる仕組みがあり、オブジェクトが生成されると自動でワーキングメモリに登録されます。
自動登録のためにはstandard-kb-object
のサブクラスにする必要があるためクラス定義でmixinしておきます。
ワーキングメモリに処理対象を全件載せるためには、SQLでクエリしオブジェクトを生成→ワーキングメモリに載る、の手順を踏みます。
(def-view-class newly_confirmed_cases_daily (standard-db-object standard-kb-object)
((id :type integer :db-kind :key :column rowid) ;sqlite3では自動でrowidというのができる
(date :type T)
(prefecture :type T)
(newly_confirmed_cases :type integer))
(:base-table |newly_confirmed_cases_daily|));; ワーキングメモリに処理対象を全件載せる
(select 'newly_confirmed_cases_daily)
;;愛知の2021年9月の合計
(let ((query '(and (newly_confirmed_cases_daily ?
date ?date
prefecture "Aichi"
newly_confirmed_cases ?cases)
((search "2021-09-" ?date) 0))))
(reduce #'+ (findall '?cases query)))
→ 21255
;;都道府県ごとの合計
(let ((tab (make-hash-table :test #'equal)))
(findall nil
`(and
(newly_confirmed_cases_daily ?x
prefecture ?pref
newly_confirmed_cases ?cases)
((incf (gethash ?pref ,tab 0) ?cases) ?)))
(loop :for pref :being :the :hash-keys :of tab :using (:hash-value cases)
:repeat 2
:collect (list pref cases)))
→ (("Shizuoka" 26308) ("Shiga" 12388))
KnowledgeWorksでは、未だに開発版扱いのAllegro Prologよりは、オブジェクトシステムとPrologの融合がより進んでいます。
ワーキングメモリに載せて処理するというのがちょっと特殊ですがPrologだけでなく、前向き推論部とも連携できますし多分強力なのでしょう。
知識ベース+前向き推論+後ろ向き推論をRDB+OPS5+Prologで実現したようなシステムは、1980年代後半のエキスパートシステムの割と標準的な構成だったようです。
1990年に登場したKnowledgeWorksもそういう流れの一つなのかなと思いますが、現在でも、AllegroGraphのようなグラフDB(知識ベース)の流れに脈々と受け継がれているかと思います。
■
HTML generated by 3bmd in LispWorks 7.0.0