今日はルールについて書きます
ルールとは
クエリーが複雑になるにつれて:where
句に内容の重複が発生してきます。その無駄を省くために再利用可能な条件式を定義できます。
ルールはいくつかの条件式を名前をつけてまとめたもので、:where
句の中で利用することができます。
例::community
のうち:community/type
が:community.type/twitter
のものを選び出すルール
[[twitter ?community] [?community :community/type :community.type/twitter]]
ルールはリストのリストで記述されます。
- 最初のリストはヘッドと呼び、ルールの名前と引数を定義します
- 残りのリストではルールを構成する条件式を記述します
上記のルールの例は、twitter
という名前を持ち、?community
という入力の引数を取ります。そして本体部分の条件式ではエンティティ?community
の要素:community/type
が:community.type/twitter
であるかテストをします。
このルールは出力となる引数はありません。その代わりに?community
が条件をみたすかどうかの真偽値を返します。
こうした複数のルールをリストにまとめてルールセットを構成することで実際のクエリーで使うことができます。
[[[twitter ?community] [?community :community/type :community.type/twitter]]]
ルールセットをクエリーに適用する方法は以下のとおりです。
- ルールセットをクエリーに入力ソースとして渡して、
:in
句の中でそれを%
で受け取ります :where
句の中でルールを呼び出します
ルールを呼び出す式は次のとおりです
(rule-name rule-arguments*)
ルールの呼び出しはルール名と引数のリストです。(なお、丸括弧で記述していますが、角括弧でも呼び出すことは可能らしいです。)
@Test void ルールの書き方使い方() { // ルール名の定義 def ruleName = 'twitter' def argument = '?arg' def head = "[${ruleName} ${argument}]" // ルールの定義 def condition = '[?arg :community/type :community.type/twitter]' def conditions = [condition] def rule = "[${head} ${conditions.join('')}]" // ルールはルールのリストで定義される def rules = [rule] def rulesDefinition = "[${rules.join('')}]" as String // ルール呼び出し def ruleArgument = '?community' def ruleInvocation = "(${ruleName} ${ruleArgument})" // 最終的なクエリー def query = $/[ :find [?name ...] :in $ % :where [${ruleArgument} :community/name ?name] ${ruleInvocation}]/$ as String def results = Peer.query(query, db, rulesDefinition) assert results.size() == 6 LOG.debug "[${test.methodName}] results: [${results.join(', ')}]" }
このサンプルコードで実行するクエリは次のようになります。
[ :find [?name ...] :in $ % :where [?community :community/name ?name] (twitter ?community)]
適用するルールは次のとおりです。
[[[twitter ?arg] [?arg :community/type :community.type/twitter]]]
実行結果はこのようになります。
results: [Magnolia Voice, Columbia Citizens, Discover SLU, Fremont Universe, Maple Leaf Life, MyWallingford]
複雑なルール
:community
→ :neighborhood
→ :district
→ :region
とたどって、:region
で絞込をかけたい場合も、結合条件の式をルール化することでクエリーが単純になります。
@Test void 少し複雑なルールの例() { // :community -> :neighborhood -> :district -> :region でマッチング def rule = $/[ [region ?community ?region] [?community :community/neighborhood ?nbr] [?nbr :neighborhood/district ?dist] [?dist :district/region ?dir] [?dir :db/ident ?region]]/$ def rules = "[${[rule].join('')}]" as String def rules = "[${[rule].join('')}]" as String // 指定した:district/regionにマッチする:community/nameを取得するクエリー def query = $/[ :find [?name ...] :in $ ?reg % :where [?community :community/name ?name] (region ?community ?reg)]/$ as String [':region/ne', ':region/sw'].each {region -> def results = Peer.query(query, db, region, rules) LOG.debug "[${test.methodName}] results: [size: [${results.size()}], values: [${results.join(', ')}]]" } }
実行結果
results: [region:[:region/ne], size: [9], values: [Maple Leaf Community Council, Hawthorne Hills Community Website, KOMO Communities - View Ridge, KOMO Communities - U-District, Magnuson Community Garden, Laurelhurst Community Club, Aurora Seattle, Maple Leaf Life, Magnuson Environmental Stewardship Alliance]] results: [region:[:region/sw], size: [34], values: [Beach Drive Blog, KOMO Communities - Green Lake, Delridge Produce Cooperative, Alki News, Longfellow Creek Community Website, Licton Springs Neighborhood , Friends of Green Lake, KOMO Communities - Wallingford, MyWallingford, Greenwood Community Council Announcements, Alki News/Alki Community Council, Nature Consortium, Broadview Community Council, KOMO Communities - West Seattle, ArtsWest, Community Harvest of Southwest Seattle, Highland Park Improvement Club, Morgan Junction Community Association, Admiral Neighborhood Association, Greenwood Community Council, Greenwood Community Council Discussion, Genesee-Schmitz Neighborhood Council, Fauntleroy Community Association, KOMO Communities - Greenwood-Phinney, Delridge Grassroots Leadership, Greenlake Community Wiki, Highland Park Action Committee, Greenwood Aurora Involved Neighbors, Greenwood Phinney Chamber of Commerce, Junction Neighborhood Organization, Delridge Neighborhoods Development Association, Greenwood Blog, My Greenlake Blog, Greenlake Community Council]]
同じルール名
同じルール名を持つルールをルールセットに入れることで「OR」検索もできます。
@Test void 同じ名前のルールも設定できる() { // 同じ名前で条件が異なるルールを定義 def ruleName = 'social-media' def argument = '?community' def rules = [$/[ [${ruleName} ${argument}] [?community :community/type :community.type/twitter]]/$, $/[ [${ruleName} ${argument}] [?community :community/type :community.type/facebook-page]]/$] def ruleDefinition = "[${rules.join('\n')}]" as String // クエリー def query = $/[ :find [?name ...] :in $ % :where [?community :community/name ?name] (social-media ?community)]/$ as String def results = Peer.query(query, db, ruleDefinition) assert results.size() == 9 LOG.debug "[${test.methodName}] results: [size: [${results.size()}], value: [${results.join(', ')}]]" }
上記のルールセットはsocial-media
という同じ名前を持つルールが二つ定義されています。一つは:community/type
が:community.type/twitter
であるかテストするルールであり、もう一つは:community/type
が:community.type/facebook-page
のものであるかをテストするルールです。
これと同様で個別に検索を行った結果は過去のエントリーにあるので、結果の数は9
であることが想定されます。
これを実行するとassert
はとおり、次のように表示されます。
results: [size: [9], value: [Magnolia Voice, Columbia Citizens, Discover SLU, Fauntleroy Community Association, Eastlake Community Council, Fremont Universe, Maple Leaf Life, MyWallingford, Blogging Georgetown]]
ルールは他のルールを適用できる
ルールはルールセット中の他のルールを適用することができます。
@Test void ルールの中で他のルールを適用する() { // regionのルール def region = $/[ [region ?community ?region] [?community :community/neighborhood ?nbr] [?nbr :neighborhood/district ?dist] [?dist :district/region ?dir] [?dir :db/ident ?region]]/$ // ソーシャルメディアのルール def socialMedia = ['twitter', 'facebook-page'].collect{"/${it}"}.collect { $/[[social-media ?community] [?community :community/type :community.type${it}]]/$ }.join('\n ') // ルールregionを適用するルール def applyingRegion = [ [ruleName: 'northern', region: ':region/ne'], [ruleName: 'northern', region: ':region/n'], [ruleName: 'northern', region: ':region/nw'], [ruleName: 'southern', region: ':region/sw'], [ruleName: 'southern', region: ':region/s'], [ruleName: 'southern', region: ':region/se'] ].collect { "[[${it.ruleName} ?arg] (region ?arg ${it.region})]" }.join('\n ') // 最終的なルール def rulesDefinition = $/[ ${region} ${socialMedia} ${applyingRegion}]/$ as String LOG.debug "[${test.methodName}] : rule\n${rulesDefinition}" // クエリ実行 def results = Peer.query($/[ :find [?name ...] :in $ % :where [?community :community/name ?name] (southern ?community) (social-media ?community)]/$ as String, db, rulesDefinition) assert results.size() == 4 LOG.debug "[${test.methodName}] results: [size: [${results.size()}], value: [${results.join(', ')}]]" }
「ルールregionを適用するルール」というコメント以降で定義されているルールでは、その内部で「region
のルール」で定義されているregion
を適用しています。
[[northern ?arg] [region ?arg :region/ne]]
このようなルールセットで一緒に定義されている他のルールを適用することが可能です。
なお実行結果は次のようになります。
ルールセットの定義
[ [ [region ?community ?region] [?community :community/neighborhood ?nbr] [?nbr :neighborhood/district ?dist] [?dist :district/region ?dir] [?dir :db/ident ?region]] [[social-media ?community] [?community :community/type :community.type/twitter]] [[social-media ?community] [?community :community/type :community.type/facebook-page]] [[northern ?arg] (region ?arg :region/ne)] [[northern ?arg] (region ?arg :region/n)] [[northern ?arg] (region ?arg :region/nw)] [[southern ?arg] (region ?arg :region/sw)] [[southern ?arg] (region ?arg :region/s)] [[southern ?arg] (region ?arg :region/se)]]
実行結果の出力
results: [size: [4], value: [Columbia Citizens, Fauntleroy Community Association, MyWallingford, Blogging Georgetown]]
昨日、今日と関数、ルールという、うまく活用すればかなりパワフルな表現を学習しました。
明日はdatomicの売りである時間軸とうまくやっていく方法を学んでいきます。
おわり