# HG changeset patch # User jbe # Date 1408927967 -7200 # Node ID adf671e3458c4e2bd0278b1607699477d91cb08f # Parent 4f82d3b3dbd9bc15af7cc0e0c6d793e8a210214f Added section "Motivation" to README diff -r 4f82d3b3dbd9 -r adf671e3458c README --- a/README Sun Aug 24 22:32:57 2014 +0200 +++ b/README Mon Aug 25 02:52:47 2014 +0200 @@ -1,5 +1,5 @@ -seqlua: Extended sequences and iterators in Lua -=============================================== +seqlua: Extension for handling sequential data in Lua +===================================================== This is an experimental package to extend Lua in the following manner: @@ -15,6 +15,122 @@ ``__ipairs`` metamethod takes precedence over ``__index``, while the ``__len`` metamethod is never used. +Metamethod handling in detail is explained in the last section +("Respected metamethods") at the bottom of this README. + + + +Motivation +---------- + +Sequential data (such as arrays or streams) is often represented in two +different ways: + +* as an ordered set of values (usually implemented as an array in other + programming languages, or as a sequence in Lua: a table with numeric keys + {1..n} associated with a value each), +* as some sort of data stream (sometimes implemented as a class of objects + providing certain methods, or as an iterator function in Lua: a function that + returns the next value with every call, where nil indicates the end of the + stream). + +Quite often, when functions work on sequential data, it shouldn't matter in +which form the sequential data is being provided to the function. As an +example, consider a function that is writing a sequence of strings to a file. +Such function could either be fed with an array of strings (a table with +numeric keys in Lua) or with a (possibly infinite) stream of data (an iterator +function in Lua). + +A function in Lua that accepts a table, might look like as follows: + + function write_lines(lines) + for i, line in ipairs(lines) do + io.stdout:write(line) + io.stdout:write("\n") + end + end + +In contrast, a function in Lua that accepts an iterator function would have to +be implemented differently: + + function write_lines(get_next_line) + for line in get_next_line do + io.stdout:write(line) + io.stdout:write("\n") + end + end + +If one wanted to create a function that accepts either a sequence in form of a +table or an iterator function, then one might need to write: + + function write_lines(lines) + local iter1, iter2, iter3 + if type(lines) == "function" then + iter1 = lines + else + iter1, iter2, iter3 = ipairs(lines) + end + for i, line in iter1, iter2, iter3 do + io.stdout:write(line) + io.stdout:write("\n") + end + end + +Obviously, this isn't something we want to write in every function that accepts +sequential data. Therefore, we usually decide for one of the two first forms +and therefore disallow the other possible representation of sequential data to +be passed to the function. + +This extension, however, modifies Lua's ``ipairs`` statement in such way that +it automatically accepts either a table or an iterator function as argument. +Thus, the first of the three functions above will accept both (table) sequences +and (function) iterators. + +In addition to the modification of ``ipairs``, it also provides C functions and +macros to iterate over values in the same manner as a generic loop statement +with ``ipairs`` would do. + +Note that this extension doesn't aim to supersede Lua's concept of iterator +functions. While metamethods (see section "Respected metamethods" below) may be +used to customize iteration behavior on values, this extension isn't thought to +replace the common practice to use function closures as iterators. Consider the +following example: + + local result = sql_query("SELECT * FROM actor ORDER BY birthdate") + write_lines(result:get_column_entries("name")) + +The ``get_column_entries`` method can return a simple function closure that +returns the next entry in the "name" column (returning ``nil`` to indicate the +end). Such a closure can then be passed to another function that iterates +through a sequence of values by invoking ``ipairs`` with the general for-loop +(as previously shown). + +Where desired, it is also possible to use metamethods to customize iteration +behavior. Consider: + + function process_row(database_row) + -- some function + end + local result = sql_query("SELECT * FROM actor") + process_row(result) -- without writing :rows() or similar + +This extension, however, doesn't respect the ``__len`` metamethod due to the +following reasons: + +* An efficient implementation seems to be impossible, where + ``for i, v in ipairs(tbl) do ... end`` does neither create a closure + nor repeatedly evaluate ``#tbl``. +* Respecting ``__len`` could be used to implement sparse arrays, but this would + require iterating functions to expect ``nil`` as a potential value. This may + lead to problems because ``nil`` is usually also used to indicate the absence + of a value. + +If such behavior is desired, though, it can still be implemented through the +``__ipairs`` metamethod. + +Unless manually done by the user in the ``__ipairs`` metamethod, this extension +never creates any closures or other values that need to be garbage collected. + Lua part of the library @@ -94,6 +210,7 @@ -- prints: alpha,beta + C part of the library --------------------- @@ -144,6 +261,7 @@ ``seqlua_iterloopauto`` is used. + Respected metamethods ---------------------