seqlua

view README @ 39:40de0fd016c4

Changed word order in one sentence in README
author jbe
date Mon Aug 25 03:11:22 2014 +0200 (2014-08-25)
parents acc53a77fdc9
children 21230b38d858
line source
1 seqlua: Extension for handling sequential data in Lua
2 =====================================================
4 This is an experimental package to extend Lua in the following manner:
6 * allow ``ipairs(seq)`` to accept either tables or functions (i.e function
7 iterators) as an argument,
8 * add a new function ``string.concat(separator, seq)`` that concats either
9 table entries or function return values,
10 * provide auxiliary C functions and macros to simplify iterating over both
11 tables and iterator functions with a generic statement.
13 Existing ``__ipairs`` or ``__index`` (but not ``__len``) metamethods are
14 respected by both the Lua functions and the C functions and macros. The
15 ``__ipairs`` metamethod takes precedence over ``__index``, while the
16 ``__len`` metamethod is never used.
18 Metamethod handling in detail is explained in the last section
19 ("Respected metamethods") at the bottom of this README.
23 Motivation
24 ----------
26 Sequential data (such as arrays or streams) is often represented in two
27 different ways:
29 * as an ordered set of values (usually implemented as an array in other
30 programming languages, or as a sequence in Lua: a table with numeric keys
31 {1..n} associated with a value each),
32 * as some sort of data stream (sometimes implemented as a class of objects
33 providing certain methods, or as an iterator function in Lua: a function that
34 returns the next value with every call, where nil indicates the end of the
35 stream).
37 Quite often, when functions work on sequential data, it shouldn't matter in
38 which form the sequential data is being provided to the function. As an
39 example, consider a function that is writing a sequence of strings to a file.
40 Such function could either be fed with an array of strings (a table with
41 numeric keys in Lua) or with a (possibly infinite) stream of data (an iterator
42 function in Lua).
44 A function in Lua that accepts a table, might look like as follows:
46 function write_lines(lines)
47 for i, line in ipairs(lines) do
48 io.stdout:write(line)
49 io.stdout:write("\n")
50 end
51 end
53 In contrast, a function in Lua that accepts an iterator function would have to
54 be implemented differently:
56 function write_lines(get_next_line)
57 for line in get_next_line do
58 io.stdout:write(line)
59 io.stdout:write("\n")
60 end
61 end
63 If one wanted to create a function that accepts either a sequence in form of a
64 table or an iterator function, then one might need to write:
66 function write_lines(lines)
67 local iter1, iter2, iter3
68 if type(lines) == "function" then
69 iter1 = lines
70 else
71 iter1, iter2, iter3 = ipairs(lines)
72 end
73 for i, line in iter1, iter2, iter3 do
74 io.stdout:write(line)
75 io.stdout:write("\n")
76 end
77 end
79 Obviously, this isn't something we want to write in every function that accepts
80 sequential data. Therefore, we usually decide for one of the two first forms
81 and therefore disallow the other possible representation of sequential data to
82 be passed to the function.
84 This extension, however, modifies Lua's ``ipairs`` statement in such way that
85 it automatically accepts either a table or an iterator function as argument.
86 Thus, the first of the three functions above will accept both (table) sequences
87 and (function) iterators.
89 In addition to the modification of ``ipairs``, it also provides C functions and
90 macros to iterate over values in the same manner as a generic loop statement
91 with ``ipairs`` would do.
93 Note that this extension doesn't aim to supersede Lua's concept of iterator
94 functions. While metamethods (see section "Respected metamethods" below) may be
95 used to customize iteration behavior on values, this extension isn't thought to
96 replace the common practice to use function closures as iterators. Consider the
97 following example:
99 local result = sql_query("SELECT * FROM actor ORDER BY birthdate")
100 write_lines(result:get_column_entries("name"))
102 The ``get_column_entries`` method can return a simple function closure that
103 returns the next entry in the "name" column (returning ``nil`` to indicate the
104 end). Such a closure can then be passed to another function that iterates
105 through a sequence of values by invoking ``ipairs`` with the general for-loop
106 (as previously shown).
108 Where desired, it is also possible to use metamethods to customize iteration
109 behavior. Consider:
111 function process_row(database_row)
112 -- some function
113 end
114 local result = sql_query("SELECT * FROM actor")
115 process_row(result) -- without writing :rows() or similar
117 This extension, however, doesn't respect the ``__len`` metamethod due to the
118 following reasons:
120 * An efficient implementation where ``for i, v in ipairs(tbl) do ... end`` does
121 neither create a closure nor repeatedly evaluate ``#tbl`` seems to be
122 impossible.
123 * Respecting ``__len`` could be used to implement sparse arrays, but this would
124 require iterating functions to expect ``nil`` as a potential value. This may
125 lead to problems because ``nil`` is usually also used to indicate the absence
126 of a value.
128 If such behavior is desired, though, it can still be implemented through the
129 ``__ipairs`` metamethod.
131 Unless manually done by the user in the ``__ipairs`` metamethod, this extension
132 never creates any closures or other values that need to be garbage collected.
136 Lua part of the library
137 -----------------------
139 The modified ``ipairs(seq)`` and the new ``string.concat(sep, seq)`` functions
140 accept either a table or a function as ``seq``. This is demonstrated in the
141 following examples:
143 require "seqlua"
145 t = {"a", "b", "c"}
147 for i, v in ipairs(t) do
148 print(i, v)
149 end
150 -- prints:
151 -- 1 a
152 -- 2 b
153 -- 3 c
155 print(string.concat(",", t))
156 -- prints: a,b,c
158 function alphabet()
159 local letter = nil
160 return function()
161 if letter == nil then
162 letter = "a"
163 elseif letter == "z" then
164 return nil
165 else
166 letter = string.char(string.byte(letter) + 1)
167 end
168 return letter
169 end
170 end
172 for i, v in ipairs(alphabet()) do
173 print(i, v)
174 end
175 -- prints:
176 -- 1 a
177 -- 2 b
178 -- 3 c
179 -- ...
180 -- 25 y
181 -- 26 z
183 print(string.concat(",", alphabet()))
184 -- prints: a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
186 function filter(f)
187 return function(seq)
188 return coroutine.wrap(function()
189 for i, v in ipairs(seq) do f(v) end
190 end)
191 end
192 end
194 alpha_beta_x = filter(function(v)
195 if v == "a" then
196 coroutine.yield("alpha")
197 elseif v == "b" then
198 coroutine.yield("beta")
199 elseif type(v) == "number" then
200 for i = 1, v do
201 coroutine.yield("X")
202 end
203 end
204 end)
206 print((","):concat(alpha_beta_x{"a", 3, "b", "c", "d"}))
207 -- prints: alpha,X,X,X,beta
209 print((","):concat(alpha_beta_x(alphabet())))
210 -- prints: alpha,beta
214 C part of the library
215 ---------------------
217 In ``seqlualib.h``, the following macro is defined:
219 #define seqlua_iterloop(L, iter, idx) \
220 for ( \
221 seqlua_iterinit((L), (iter), (idx)); \
222 seqlua_iternext(iter); \
223 )
225 and
227 #define seqlua_iterloopauto(L, iter, idx) \
228 for ( \
229 seqlua_iterinit((L), (iter), (idx)); \
230 seqlua_iternext(iter); \
231 lua_pop((L), 1) \
232 )
234 This macro allows iteration over either tables or iterator functions as the
235 following example function demonstrates:
237 int printcsv(lua_State *L) {
238 seqlua_Iterator iter;
239 seqlua_iterloop(L, &iter, 1) {
240 if (seqlua_itercount(&iter) > 1) fputs(",", stdout);
241 fputs(luaL_tolstring(L, -1, NULL), stdout);
242 // two values need to be popped (the value pushed by
243 // seqlua_iternext and the value pushed by luaL_tolstring)
244 lua_pop(L, 2);
245 }
246 fputs("\n", stdout);
247 return 0;
248 }
250 printcsv{"a", "b", "c"}
251 -- prints: a,b,c
253 printcsv(assert(io.open("testfile")):lines())
254 -- prints: line1,line2,... of "testfile"
256 NOTE: During iteration using ``seqlua_iterloop``, ``seqlua_iterloopauto``, or
257 ``seqlua_iterinit``, three extra elements are stored on the stack (additionally
258 to the value). These extra elements are removed automatically when the loop ends
259 (i.e. when ``seqlua_iternext`` returns zero). The value pushed onto the stack
260 for every iteration step has to be removed manually from the stack, unless
261 ``seqlua_iterloopauto`` is used.
265 Respected metamethods
266 ---------------------
268 Regarding the behavior of the Lua functions and the C functions and macros
269 provided by this extension, an existing ``__index`` metamethod will be
270 respected automatically. An existing ``__ipairs`` metamethod, however, takes
271 precedence.
273 If the ``__ipairs`` field of a value's metatable is set, then it must always
274 refer to a function. When starting iteration over a value with such a
275 metamethod being set, then this function is called with ``self`` (i.e. the
276 value itself) passed as first argument. The return values of the ``__ipairs``
277 metamethod may take one of the following 4 forms:
279 * ``return function_or_callable, static_argument, startindex`` causes the three
280 arguments to be returned by ``ipairs`` without further modification. Using
281 the C macros and functions for iteration, the behavior is according to the
282 generic loop statement in Lua:
283 ``for i, v in function_or_callable, static_argument, startindex do ... end``
284 * ``return "raw", table`` will result in iteration over the table ``table``
285 using ``lua_rawgeti``
286 * ``return "index", table_or_userdata`` will result in iteration over the table
287 or userdata while respecting any ``__index`` metamethod of the table or
288 userdata value
289 * ``return "call", function_or_callable`` will use the callable value as
290 (function) iterator where the function is expected to return a single value
291 without any index (the index is inserted automatically when using the
292 ``ipairs`` function for iteration)
294 These possiblities are demonstrated by the following example code:
296 require "seqlua"
298 do
299 local function ipairsaux(t, i)
300 i = i + 1
301 if i <= 3 then
302 return i, t[i]
303 end
304 end
305 custom = setmetatable(
306 {"one", "two", "three", "four", "five"},
307 {
308 __ipairs = function(self)
309 return ipairsaux, self, 0
310 end
311 }
312 )
313 end
314 print(string.concat(",", custom))
315 -- prints: one,two,three
316 -- (note that "four" and "five" are not printed)
318 tbl = {"alpha", "beta"}
320 proxy1 = setmetatable({}, {__index = tbl})
321 for i, v in ipairs(proxy1) do print(i, v) end
322 -- prints:
323 -- 1 alpha
324 -- 2 beta
326 proxy2 = setmetatable({}, {
327 __ipairs = function(self)
328 return "index", proxy1
329 end
330 })
331 for i, v in ipairs(proxy2) do print(i, v) end
332 -- prints:
333 -- 1 alpha
334 -- 2 beta
335 print(proxy2[1])
336 -- prints: nil
338 cursor = setmetatable({
339 "alice", "bob", "charlie", pos=1
340 }, {
341 __call = function(self)
342 local value = self[self.pos]
343 if value == nil then
344 self.pos = 1
345 else
346 self.pos = self.pos + 1
347 end
348 return value
349 end,
350 __ipairs = function(self)
351 return "call", self
352 end
353 })
354 for i, v in ipairs(cursor) do print(i, v) end
355 -- prints:
356 -- 1 alice
357 -- 2 bob
358 -- 3 charlie
359 print(cursor())
360 -- prints: alice
361 for i, v in ipairs(cursor) do print(i, v) end
362 -- prints:
363 -- 1 bob
364 -- 2 charlie
365 -- (note that "alice" has been returned earlier)
367 coefficients = setmetatable({1.25, 3.14, 17.5}, {
368 __index = function(self) return 1 end,
369 __ipairs = function(self) return "raw", self end
370 })
371 for i, v in ipairs(coefficients) do print(i, v) end
372 -- prints:
373 -- 1 1.25
374 -- 2 3.14
375 -- 3 17.5
376 -- (note that iteration terminates even if coefficients[4] == 1)
377 print(coefficients[4])
378 -- prints: 1

Impressum / About Us