3

Is there / please suggest a syntax to achieve a compact 'test and assign' in lua?

Consider this segment from luasql examples ( http://keplerproject.org/luasql/examples.html )

-- retrieve a cursor
cur = assert (con:execute"SELECT name, email from people")
-- print all rows, the rows will be indexed by field names
row = cur:fetch ({}, "a")
while row do
  print(string.format("Name: %s, E-mail: %s", row.name, row.email))
  -- reusing the table of results
  row = cur:fetch (row, "a")
end

I am learning lua, and really struggling to accept the duplicated call to cur:fetch(). I see that repeat/until trivially fixes the issue, but then it seems I have to test twice:

repeat
  row = cur:fetch ({}, "a")
  if row then
    print ...
  end
until nil == row

I consider this less error prone for the case of 'row = ...' getting more complex, but still seems inelegant.

hjpotter92
  • 78,589
  • 36
  • 144
  • 183
robm
  • 1,303
  • 1
  • 16
  • 24

3 Answers3

4

You can simplify the first loop to this:

local row = {}
while row do
  row = cur:fetch(row, "a")
end

EDIT: The sample page hits at a possible solution using iterators and a for loop, like @doukremt is suggesting. Unfortunately it also contains a couple errors (it assumes that the rows get "unpacked" in the second example but not in the first, does not declare everything local, and omits more parenthesis than it's sane to in a sample code). Here's how you can transform a cursor in an iterator:

local iterate = function(cur)
  return function() return cur:fetch() end
end

Here's how you use it:

local cur = assert(con:execute("SELECT name, email from people"))

for row in iterate(cur) do
  print(string.format ("%s: %s", row.name, row.email))
end

I must warn you that iterators are in general more expensive than while/repeat loops. Execute performance tests if you are worried about speed.

kikito
  • 51,734
  • 32
  • 149
  • 189
  • this seems equivalent to the repeat/until form with a different initialisation? it successfully eliminates one of the cur:fetch() calls, but still requires a redundant test of the result before using it (printing, in this case). – robm Oct 20 '14 at 13:36
  • Thank You! I had tried to implement an equivalent to the iterator section in the sample but could not get it to work - your complete example is especially helpful. Minor typo (s/cur/curr/) in line 3 of "here's how you use it". – robm Oct 21 '14 at 07:56
  • Glad it helped you. I fixed the typo – kikito Oct 21 '14 at 08:36
3

Just use a for loop:

row = {}
for row  in cur:fetch(row, "a") do
    -- ...
end

The call to fetch() returns a pointer to the table you pass as argument, so you could as well do:

row = {}
for foobar in cur:fetch(row, "a") ...

The important point is that fetch() returns nil when there are no more rows, so you just need to check for that, which a for loop does implicitly.

michaelmeyer
  • 7,985
  • 7
  • 30
  • 36
  • Have tried several variants here and generally get 'attempt to call a table value' at the for / in line? – robm Oct 20 '14 at 14:29
3

Try

while true do
  local row = cur:fetch ({}, "a")
  if row then
    print ...
  else
    break
  end
end
lhf
  • 70,581
  • 9
  • 108
  • 149
  • 1
    my preference would be something like `while true do local row = cur:fetch ({}, "a") if not row then break end` (sorry can't work out multiline code formatting in comments) but it is still not the readability that I was hoping for – robm Oct 21 '14 at 08:16
  • would +1 a second time if I could as this, with my 1-line break modification above, is actually the form I am using in my code (due to kikito's comments about performance). – robm Oct 22 '14 at 10:34