読者です 読者をやめる 読者になる 読者になる

プログラミングの魔物

エラー、バグ、仕様変更と戦うブログ

Luaでレコードアクセス2

前回の記事ではレコードの集合から条件に一致したものを抽出するメタテーブルを作成した。
しかし前回の方法は条件と等しいレコード("==")の抽出にしか対応していないため、あまり有用ではない。
そこで今回は抽出方法を拡張し、もっと柔軟にレコード操作を行えるようにする。

クエリクラスの作成

レコード集合に対する操作を提供するクラス。操作対象はテーブルの配列。
基本的にはレコードのメタテーブルとして使用するが、メタテーブルにしなくても利用可能。
レコード集合に対して抽出・巡回、抽出結果に対する絞込みなどを行うことができる。

クエリクラスのレコード文法を規定する

比較演算子による抽出

--age==20のレコードを抽出する
result = records().age.eq(20)

--ageが20以外のレコードを抽出する
result = records().age.ne(20)

抽出記法一覧

比較演算子 抽出記法
== eq
~= ne
< lt
> rt
<= le
>= re

抽出結果を絞り込む

--age==20でname=="takashi"のレコードを抽出する
result = records().age.eq(20)().name.eq("takashi")

抽出条件を関数で指定する

--age==20のレコードを抽出する
result = records % function(t) return t.age == 20 end

レコードを巡回する

--すべてのレコードのageに1を足す
records(function (t) t.age = t.age + 1 end)

クエリクラス(query.lua

Query = {}

--抽出条件のクロージャ
local Conditions = {
	eq = function (key, value) return function (t) return t[key] == value end end,
	ne = function (key, value) return function (t) return t[key] ~= value end end,
	lt = function (key, value) return function (t) return t[key] < value end end,
	rt = function (key, value) return function (t) return t[key] > value end end,
	le = function (key, value) return function (t) return t[key] <= value end end,
	re = function (key, value) return function (t) return t[key] >= value end end,
}

--レコード集合にクエリクラスをセットする
function Query:set(t)
	return setmetatable(t, Query)
end

--レコードを巡回する
function Query:each(func)
	for i, v in ipairs(self) do
		func(v)
	end
end

--レコードを抽出する
function Query:__mod(func)
	local result = {}
	for i, v in ipairs(self) do
		if func(v) then
			table.insert(result, v)
		end
	end
	return setmetatable(result, Query)
end

--関数のように()で呼び出された時に巡回または抽出を行う
function Query:__call(value)
	if type(value) == "function" then
		Query.each(self, value)
		value = nil
	end
	local records = value or self
	return setmetatable({}, {__index = function (t, field)
		return setmetatable({}, {__index = function (t, key)
			return function (value)
				return Query.__mod(records, Conditions[key](field, value))
			end
		end})
	end})
end

return setmetatable(Query, Query)

サンプル1 : ageが20以外のレコードを抽出

require "query"

records = {
	{name="takashi", age=20},
	{name="takeshi", age=21},
	{name="takei", age=22},
	{name="taki", age=20},
	{name="taku", age=19},
}

--メタテーブルをセット
Query:set(records)

--抽出
result = records().age.ne(20)
--result = Query(records).age.ne(20)  --メタテーブルをセットしない場合

for i, v in ipairs(result) do
	print(v.name, v.age)
end

サンプル1の出力結果

takeshi	21
takei	22
taku	19

サンプル2 : 条件関数によりage==20のレコードを抽出

--<メタテーブルをセットする所まで省略>

--条件関数を指定
result = records % function(t) return t.age == 20 end
--result = Query.__mod(records, function(t) return t.age == 20 end)  --メタテーブルをセットしない場合

for i, v in ipairs(result) do
	print(v.name, v.age)
end

サンプル2の出力結果

takashi	20
taki	20

サンプル3 : すべてのレコードのageに1を足す

--<メタテーブルをセットする所まで省略>

--巡回
records(function(t) t.age = t.age + 1 end)
--Query.each(records, function(t) t.age = t.age + 1 end)  --メタテーブルをセットしない場合

for i, v in ipairs(records) do
	print(v.name, v.age)
end

サンプル3の出力結果

takashi	21
takeshi	22
takei	23
taki	21
taku	20

メモ

  • 抽出結果のメタテーブルにはクエリクラスがセットされるので、直接巡回することもできる
--age==22のレコードを表示
records().age.eq(22)(function(t) print(t.name, t.age) end)
--> takei	22
  • 作成したはいいが利用シーンが特に思い浮かばない。まあ良いか。