seqlua

annotate README @ 56:c3976eacc6ab

Clarified/simplified examples in README
author jbe
date Wed Aug 27 00:31:43 2014 +0200 (2014-08-27)
parents 37c2841c6e8c
children
rev   line source
jbe@37 1 seqlua: Extension for handling sequential data in Lua
jbe@37 2 =====================================================
jbe@0 3
jbe@55 4 This package is an experimental extension for the Lua 5.2 programming language
jbe@54 5 which:
jbe@0 6
jbe@54 7 * allows ``ipairs(seq)`` to accept either tables or functions (i.e function
jbe@54 8 iterators) as an argument,
jbe@49 9 * adds a new function ``string.concat(separator, seq)`` that concats either
jbe@32 10 table entries or function return values,
jbe@49 11 * provides auxiliary C functions and macros to simplify iterating over both
jbe@38 12 tables and iterator functions with a generic statement.
jbe@0 13
jbe@33 14 Existing ``__ipairs`` or ``__index`` (but not ``__len``) metamethods are
jbe@33 15 respected by both the Lua functions and the C functions and macros. The
jbe@32 16 ``__ipairs`` metamethod takes precedence over ``__index``, while the
jbe@32 17 ``__len`` metamethod is never used.
jbe@32 18
jbe@37 19 Metamethod handling in detail is explained in the last section
jbe@37 20 ("Respected metamethods") at the bottom of this README.
jbe@37 21
jbe@49 22 In Lua, this extension is loaded by ``require "seqlua"``. In order to use the
jbe@49 23 auxiliary C functions and macros, add ``#include <seqlualib.h>`` to your C file
jbe@49 24 and ensure that the functions implemented in ``seqlualib.c`` are statically or
jbe@49 25 dynamically linked with your C Lua library.
jbe@49 26
jbe@37 27
jbe@37 28
jbe@37 29 Motivation
jbe@37 30 ----------
jbe@37 31
jbe@37 32 Sequential data (such as arrays or streams) is often represented in two
jbe@37 33 different ways:
jbe@37 34
jbe@37 35 * as an ordered set of values (usually implemented as an array in other
jbe@37 36 programming languages, or as a sequence in Lua: a table with numeric keys
jbe@37 37 {1..n} associated with a value each),
jbe@37 38 * as some sort of data stream (sometimes implemented as a class of objects
jbe@37 39 providing certain methods, or as an iterator function in Lua: a function that
jbe@37 40 returns the next value with every call, where nil indicates the end of the
jbe@37 41 stream).
jbe@37 42
jbe@37 43 Quite often, when functions work on sequential data, it shouldn't matter in
jbe@37 44 which form the sequential data is being provided to the function. As an
jbe@37 45 example, consider a function that is writing a sequence of strings to a file.
jbe@37 46 Such function could either be fed with an array of strings (a table with
jbe@37 47 numeric keys in Lua) or with a (possibly infinite) stream of data (an iterator
jbe@37 48 function in Lua).
jbe@37 49
jbe@37 50 A function in Lua that accepts a table, might look like as follows:
jbe@37 51
jbe@37 52 function write_lines(lines)
jbe@37 53 for i, line in ipairs(lines) do
jbe@37 54 io.stdout:write(line)
jbe@37 55 io.stdout:write("\n")
jbe@37 56 end
jbe@37 57 end
jbe@37 58
jbe@37 59 In contrast, a function in Lua that accepts an iterator function would have to
jbe@37 60 be implemented differently:
jbe@37 61
jbe@37 62 function write_lines(get_next_line)
jbe@37 63 for line in get_next_line do
jbe@37 64 io.stdout:write(line)
jbe@37 65 io.stdout:write("\n")
jbe@37 66 end
jbe@37 67 end
jbe@37 68
jbe@37 69 If one wanted to create a function that accepts either a sequence in form of a
jbe@37 70 table or an iterator function, then one might need to write:
jbe@37 71
jbe@41 72 do
jbe@41 73 local function write_line(line)
jbe@37 74 io.stdout:write(line)
jbe@37 75 io.stdout:write("\n")
jbe@37 76 end
jbe@41 77 function write_lines(lines)
jbe@41 78 if type(lines) == "function" then
jbe@41 79 for line in lines do
jbe@41 80 write_line(line)
jbe@41 81 end
jbe@41 82 else
jbe@41 83 for i, line in ipairs(lines) do
jbe@41 84 write_line(line)
jbe@41 85 end
jbe@41 86 end
jbe@41 87 end
jbe@37 88 end
jbe@37 89
jbe@41 90 Obviously, this isn't something we want to do in every function that accepts
jbe@37 91 sequential data. Therefore, we usually decide for one of the two first forms
jbe@48 92 and thus disallow the other possible representation of sequential data to be
jbe@48 93 passed to the function.
jbe@37 94
jbe@37 95 This extension, however, modifies Lua's ``ipairs`` statement in such way that
jbe@37 96 it automatically accepts either a table or an iterator function as argument.
jbe@54 97 Thus, the first of the three ``write_lines`` functions above will accept both
jbe@54 98 (table) sequences and (function) iterators.
jbe@37 99
jbe@37 100 In addition to the modification of ``ipairs``, it also provides C functions and
jbe@37 101 macros to iterate over values in the same manner as a generic loop statement
jbe@37 102 with ``ipairs`` would do.
jbe@37 103
jbe@56 104 This extension doesn't aim to supersede Lua's concept of iterator functions.
jbe@56 105 While metamethods (see section "Respected metamethods" below) may be used to
jbe@56 106 customize iteration behavior on values, this extension isn't thought to replace
jbe@56 107 the common practice to use function closures as iterators. Consider the
jbe@56 108 following example:
jbe@56 109
jbe@56 110 function write_lines(lines)
jbe@56 111 for i, line in ipairs(lines) do
jbe@56 112 io.stdout:write(line)
jbe@56 113 io.stdout:write("\n")
jbe@56 114 end
jbe@56 115 end
jbe@56 116 local result = sql_query("SELECT * FROM actor ORDER BY birthdate")
jbe@56 117 -- assert(type(result:get_column_entries("name")) == "function")
jbe@56 118 write_lines(result:get_column_entries("name"))
jbe@56 119
jbe@56 120 Note, however, that in case of repeated or nested loops, using function
jbe@56 121 iterators may not be feasible:
jbe@55 122
jbe@55 123 function print_list_twice(seq)
jbe@55 124 for i = 1, 2 do
jbe@55 125 for i, v in ipairs(seq) do
jbe@55 126 print(v)
jbe@55 127 end
jbe@55 128 end
jbe@55 129 end
jbe@55 130 print_list_twice(io.stdin:lines()) -- won't work as expected
jbe@55 131
jbe@56 132 Where desired, it is possible to use metamethods to customize iteration
jbe@44 133 behavior:
jbe@44 134
jbe@44 135 function print_rows(rows)
jbe@44 136 for i, row in ipairs(rows) do
jbe@44 137 print_row(row)
jbe@44 138 end
jbe@44 139 end
jbe@44 140 local result = sql_query("SELECT * FROM actor ORDER BY birthday")
jbe@46 141 assert(type(result) == "userdata")
jbe@44 142
jbe@44 143 -- we may rely on the ``__index`` or ``__ipairs`` metamethod to
jbe@44 144 -- iterate through all result rows here:
jbe@44 145 print_rows(result) -- no need to use ":rows()" or a similar syntax
jbe@44 146
jbe@45 147 -- but we can also still pass an individual set of result rows to the
jbe@44 148 -- print_rows function:
jbe@44 149 print_rows{result[1], result[#result]}
jbe@44 150
jbe@44 151 This extension, however, doesn't respect the ``__len`` metamethod due to the
jbe@47 152 following considerations:
jbe@37 153
jbe@39 154 * An efficient implementation where ``for i, v in ipairs(tbl) do ... end`` does
jbe@39 155 neither create a closure nor repeatedly evaluate ``#tbl`` seems to be
jbe@39 156 impossible.
jbe@37 157 * Respecting ``__len`` could be used to implement sparse arrays, but this would
jbe@37 158 require iterating functions to expect ``nil`` as a potential value. This may
jbe@37 159 lead to problems because ``nil`` is usually also used to indicate the absence
jbe@37 160 of a value.
jbe@37 161
jbe@40 162 Though, if such behavior is desired, it can still be implemented through the
jbe@37 163 ``__ipairs`` metamethod.
jbe@37 164
jbe@48 165 Unless manually done by the user in the ``__ipairs`` metamethod, the ``ipairs``
jbe@48 166 function as well as the corresponding C functions and macros provided by this
jbe@48 167 extension never create any closures or other values that need to be garbage
jbe@48 168 collected.
jbe@37 169
jbe@0 170
jbe@0 171
jbe@0 172 Lua part of the library
jbe@0 173 -----------------------
jbe@0 174
jbe@30 175 The modified ``ipairs(seq)`` and the new ``string.concat(sep, seq)`` functions
jbe@30 176 accept either a table or a function as ``seq``. This is demonstrated in the
jbe@30 177 following examples:
jbe@0 178
jbe@0 179 require "seqlua"
jbe@0 180
jbe@0 181 t = {"a", "b", "c"}
jbe@0 182
jbe@54 183 for i, v in ipairs(t) do
jbe@0 184 print(i, v)
jbe@0 185 end
jbe@0 186 -- prints:
jbe@0 187 -- 1 a
jbe@0 188 -- 2 b
jbe@0 189 -- 3 c
jbe@0 190
jbe@25 191 print(string.concat(",", t))
jbe@25 192 -- prints: a,b,c
jbe@25 193
jbe@19 194 function alphabet()
jbe@0 195 local letter = nil
jbe@0 196 return function()
jbe@0 197 if letter == nil then
jbe@19 198 letter = "a"
jbe@19 199 elseif letter == "z" then
jbe@0 200 return nil
jbe@0 201 else
jbe@0 202 letter = string.char(string.byte(letter) + 1)
jbe@0 203 end
jbe@0 204 return letter
jbe@0 205 end
jbe@0 206 end
jbe@0 207
jbe@54 208 for i, v in ipairs(alphabet()) do
jbe@0 209 print(i, v)
jbe@0 210 end
jbe@0 211 -- prints:
jbe@0 212 -- 1 a
jbe@0 213 -- 2 b
jbe@0 214 -- 3 c
jbe@0 215 -- ...
jbe@0 216 -- 25 y
jbe@0 217 -- 26 z
jbe@0 218
jbe@25 219 print(string.concat(",", alphabet()))
jbe@25 220 -- 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
jbe@25 221
jbe@26 222 function filter(f)
jbe@26 223 return function(seq)
jbe@26 224 return coroutine.wrap(function()
jbe@54 225 for i, v in ipairs(seq) do f(v) end
jbe@26 226 end)
jbe@26 227 end
jbe@0 228 end
jbe@19 229
jbe@29 230 alpha_beta_x = filter(function(v)
jbe@28 231 if v == "a" then
jbe@28 232 coroutine.yield("alpha")
jbe@28 233 elseif v == "b" then
jbe@28 234 coroutine.yield("beta")
jbe@28 235 elseif type(v) == "number" then
jbe@23 236 for i = 1, v do
jbe@28 237 coroutine.yield("X")
jbe@23 238 end
jbe@0 239 end
jbe@26 240 end)
jbe@0 241
jbe@29 242 print((","):concat(alpha_beta_x{"a", 3, "b", "c", "d"}))
jbe@28 243 -- prints: alpha,X,X,X,beta
jbe@25 244
jbe@29 245 print((","):concat(alpha_beta_x(alphabet())))
jbe@28 246 -- prints: alpha,beta
jbe@27 247
jbe@0 248
jbe@37 249
jbe@0 250 C part of the library
jbe@0 251 ---------------------
jbe@0 252
jbe@0 253 In ``seqlualib.h``, the following macro is defined:
jbe@0 254
jbe@54 255 #define seqlua_iterloop(L, iter, idx) \
jbe@0 256 for ( \
jbe@54 257 seqlua_iterinit((L), (iter), (idx)); \
jbe@0 258 seqlua_iternext(iter); \
jbe@25 259 )
jbe@25 260
jbe@25 261 and
jbe@25 262
jbe@25 263 #define seqlua_iterloopauto(L, iter, idx) \
jbe@25 264 for ( \
jbe@54 265 seqlua_iterinit((L), (iter), (idx)); \
jbe@25 266 seqlua_iternext(iter); \
jbe@0 267 lua_pop((L), 1) \
jbe@0 268 )
jbe@0 269
jbe@23 270 This macro allows iteration over either tables or iterator functions as the
jbe@23 271 following example function demonstrates:
jbe@0 272
jbe@0 273 int printcsv(lua_State *L) {
jbe@0 274 seqlua_Iterator iter;
jbe@54 275 seqlua_iterloop(L, &iter, 1) {
jbe@0 276 if (seqlua_itercount(&iter) > 1) fputs(",", stdout);
jbe@0 277 fputs(luaL_tolstring(L, -1, NULL), stdout);
jbe@25 278 // two values need to be popped (the value pushed by
jbe@25 279 // seqlua_iternext and the value pushed by luaL_tolstring)
jbe@25 280 lua_pop(L, 2);
jbe@0 281 }
jbe@0 282 fputs("\n", stdout);
jbe@0 283 return 0;
jbe@0 284 }
jbe@0 285
jbe@11 286 printcsv{"a", "b", "c"}
jbe@11 287 -- prints: a,b,c
jbe@11 288
jbe@11 289 printcsv(assert(io.open("testfile")):lines())
jbe@11 290 -- prints: line1,line2,... of "testfile"
jbe@0 291
jbe@31 292 NOTE: During iteration using ``seqlua_iterloop``, ``seqlua_iterloopauto``, or
jbe@31 293 ``seqlua_iterinit``, three extra elements are stored on the stack (additionally
jbe@31 294 to the value). These extra elements are removed automatically when the loop ends
jbe@31 295 (i.e. when ``seqlua_iternext`` returns zero). The value pushed onto the stack
jbe@31 296 for every iteration step has to be removed manually from the stack, unless
jbe@31 297 ``seqlua_iterloopauto`` is used.
jbe@0 298
jbe@31 299
jbe@37 300
jbe@35 301 Respected metamethods
jbe@35 302 ---------------------
jbe@35 303
jbe@35 304 Regarding the behavior of the Lua functions and the C functions and macros
jbe@35 305 provided by this extension, an existing ``__index`` metamethod will be
jbe@35 306 respected automatically. An existing ``__ipairs`` metamethod, however, takes
jbe@35 307 precedence.
jbe@35 308
jbe@35 309 If the ``__ipairs`` field of a value's metatable is set, then it must always
jbe@35 310 refer to a function. When starting iteration over a value with such a
jbe@35 311 metamethod being set, then this function is called with ``self`` (i.e. the
jbe@35 312 value itself) passed as first argument. The return values of the ``__ipairs``
jbe@35 313 metamethod may take one of the following 4 forms:
jbe@35 314
jbe@35 315 * ``return function_or_callable, static_argument, startindex`` causes the three
jbe@35 316 arguments to be returned by ``ipairs`` without further modification. Using
jbe@35 317 the C macros and functions for iteration, the behavior is according to the
jbe@35 318 generic loop statement in Lua:
jbe@35 319 ``for i, v in function_or_callable, static_argument, startindex do ... end``
jbe@35 320 * ``return "raw", table`` will result in iteration over the table ``table``
jbe@35 321 using ``lua_rawgeti``
jbe@35 322 * ``return "index", table_or_userdata`` will result in iteration over the table
jbe@35 323 or userdata while respecting any ``__index`` metamethod of the table or
jbe@35 324 userdata value
jbe@35 325 * ``return "call", function_or_callable`` will use the callable value as
jbe@35 326 (function) iterator where the function is expected to return a single value
jbe@35 327 without any index (the index is inserted automatically when using the
jbe@35 328 ``ipairs`` function for iteration)
jbe@35 329
jbe@35 330 These possiblities are demonstrated by the following example code:
jbe@35 331
jbe@35 332 require "seqlua"
jbe@35 333
jbe@35 334 do
jbe@35 335 local function ipairsaux(t, i)
jbe@35 336 i = i + 1
jbe@35 337 if i <= 3 then
jbe@35 338 return i, t[i]
jbe@35 339 end
jbe@35 340 end
jbe@35 341 custom = setmetatable(
jbe@35 342 {"one", "two", "three", "four", "five"},
jbe@35 343 {
jbe@35 344 __ipairs = function(self)
jbe@35 345 return ipairsaux, self, 0
jbe@35 346 end
jbe@35 347 }
jbe@35 348 )
jbe@35 349 end
jbe@35 350 print(string.concat(",", custom))
jbe@36 351 -- prints: one,two,three
jbe@35 352 -- (note that "four" and "five" are not printed)
jbe@35 353
jbe@35 354 tbl = {"alpha", "beta"}
jbe@35 355
jbe@35 356 proxy1 = setmetatable({}, {__index = tbl})
jbe@35 357 for i, v in ipairs(proxy1) do print(i, v) end
jbe@35 358 -- prints:
jbe@35 359 -- 1 alpha
jbe@35 360 -- 2 beta
jbe@35 361
jbe@35 362 proxy2 = setmetatable({}, {
jbe@35 363 __ipairs = function(self)
jbe@35 364 return "index", proxy1
jbe@35 365 end
jbe@35 366 })
jbe@35 367 for i, v in ipairs(proxy2) do print(i, v) end
jbe@35 368 -- prints:
jbe@35 369 -- 1 alpha
jbe@35 370 -- 2 beta
jbe@35 371 print(proxy2[1])
jbe@35 372 -- prints: nil
jbe@35 373
jbe@35 374 cursor = setmetatable({
jbe@35 375 "alice", "bob", "charlie", pos=1
jbe@35 376 }, {
jbe@35 377 __call = function(self)
jbe@35 378 local value = self[self.pos]
jbe@35 379 if value == nil then
jbe@35 380 self.pos = 1
jbe@35 381 else
jbe@35 382 self.pos = self.pos + 1
jbe@35 383 end
jbe@35 384 return value
jbe@35 385 end,
jbe@35 386 __ipairs = function(self)
jbe@35 387 return "call", self
jbe@35 388 end
jbe@35 389 })
jbe@35 390 for i, v in ipairs(cursor) do print(i, v) end
jbe@35 391 -- prints:
jbe@35 392 -- 1 alice
jbe@35 393 -- 2 bob
jbe@35 394 -- 3 charlie
jbe@35 395 print(cursor())
jbe@35 396 -- prints: alice
jbe@35 397 for i, v in ipairs(cursor) do print(i, v) end
jbe@35 398 -- prints:
jbe@35 399 -- 1 bob
jbe@35 400 -- 2 charlie
jbe@35 401 -- (note that "alice" has been returned earlier)
jbe@35 402
jbe@35 403 coefficients = setmetatable({1.25, 3.14, 17.5}, {
jbe@35 404 __index = function(self) return 1 end,
jbe@35 405 __ipairs = function(self) return "raw", self end
jbe@35 406 })
jbe@35 407 for i, v in ipairs(coefficients) do print(i, v) end
jbe@35 408 -- prints:
jbe@35 409 -- 1 1.25
jbe@35 410 -- 2 3.14
jbe@35 411 -- 3 17.5
jbe@35 412 -- (note that iteration terminates even if coefficients[4] == 1)
jbe@35 413 print(coefficients[4])
jbe@35 414 -- prints: 1
jbe@35 415
jbe@35 416

Impressum / About Us