lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



I've got two little things about select():

  o Remove the error for negative index too negative?

  o Change the behavior for a negative index?

I noticed that the reference manual doesn't explicitly say what happens with negative indexes in select(index, ...). The wording is "returns all arguments after argument number index". A quick try - and a look at luaB_select() in lbaselib.c - reveals that it does indeed handle negative indexes.

So there is an implicit "from the top of the stack"; that's fine and as it should be (see 3.2 Stack Size). But the current implementation allows a positive index to be greater than the stack size (returning nothing) while it throws an error if a negative one is less than minus the size of the stack.

I'm thinking that either the error should be thrown in either case (positive or negative too far from zero, or equal to zero), or else the entire list should be returned when it's negative. I've attached a patch (select-big-neg-idx-ok.patch) for the latter, which I prefer. But see the alternative behavior that I discuss later.

Some sample code to illustrate:

-- Lua 5.1.4 original:

function split(s)
   if #s == 1 then return s end
   return s:sub(1,1), split(s:sub(2))
end

print(select( 0, split('abc')))  -- stdin:1: bad argument #1 to 'select'
                                 -- (index out of range)
print(select( 1, split('abc')))  -- a b c
print(select( 2, split('abc')))  -- b c
print(select( 3, split('abc')))  -- c
print(select( 4, split('abc')))  --
print(select(-1, split('abc')))  -- c
print(select(-2, split('abc')))  -- b c
print(select(-3, split('abc')))  -- a b c
print(select(-4, split('abc')))  -- stdin:1: bad argument #1 to 'select'
                                 -- (index out of range)

-- Lua 5.1.4 with select-big-neg-idx-ok.patch applied:
-- All just as above except the last line:

print(select(-4, split('abc')))  -- a b c

That's my own expectation of what should happen.

I think an interesting alternative, though, would be for select() with a negative index to return the arguments up to (and including) the argument specified by the index, rather than from the back of the list.

So in the examples above, the following would happen for a negative index:

print(select(-1, split('abc')))  -- a b c    -- up to index -1 (top)
print(select(-2, split('abc')))  -- a b
print(select(-3, split('abc')))  -- a
print(select(-4, split('abc')))  --
print(select(-5, split('abc')))  --

What do you think?

I've attached select-up-to-neg-idx.patch for this behavior.

This would allow for things like reversing the argument order without having to use tables (or C).

I stumbled upon this idea during an attempt to write a reverse function for a vararg - trying to do it without storing anything in a table. I didn't see a way - take it as a challenge if you wish!

But if this new behavior were to be implemented, this would be possible:

function rev(...)
   local n = select('#', ...)

   if n == 0 then
      return
   else
      return select(n, ...), rev(select(-2, ...))
   end
end   -- rev()

print(rev(split('abc')))    -- c b a

This can be quite handy for a lot of purposes. My particular interest was in creating a function composition routine with the more natural functional order. I could have used a table, reversed it, then used Mark Hamburg's pipe() function as described in http://lua-users.org/lists/lua-l/2008-12/msg00324.html. But I thought it should be possible using select() and recursion. I could not do it without being able to either (1) hack off the last item in a varargs or (2) use 'return varargs, another_arg' without truncating the varargs (section 2.4.3). There are plenty of good reasons why varargs should be shortened in (2), so that left (1).

Now I can write:

-- Mark Hamburg's pipe() function:
function pipe(fn, ...)
   if select('#', ...) == 0 then
      return fn
   else
      local rest = pipe(...)
      return function(...) return rest(fn(...)) end
   end
end   -- pipe()

function comp(...)
   return pipe(rev(...))
end   -- comp()

function add(...)
   local sum = 0
   for i = 1, select('#', ...) do
      sum = sum + tonumber(select(i, ...))
   end
   return sum
end   -- add()

print(comp(add, split)('123456'))    -- 21

But with this change there are probably other ways to write comp() directly.

Doug

*** src/lbaselib.c-orig	2009-01-09 15:43:49.000000000 -0500
--- src/lbaselib.c	2009-01-09 15:59:27.000000000 -0500
***************
*** 363,371 ****
    }
    else {
      int i = luaL_checkint(L, 1);
      if (i < 0) i = n + i;
-     else if (i > n) i = n;
-     luaL_argcheck(L, 1 <= i, 1, "index out of range");
      return n - i;
    }
  }
--- 363,372 ----
    }
    else {
      int i = luaL_checkint(L, 1);
+     luaL_argcheck(L, (i <= -1) || (1 <= i), 1, "index out of range");
+     if (i <= -n) i = 1;  /* Keep all; list grows as more negative. */
+     if (i > n) i = n;
      if (i < 0) i = n + i;
      return n - i;
    }
  }
*** src/lbaselib.c-orig	2009-01-09 15:43:49.000000000 -0500
--- src/lbaselib.c	2009-01-09 17:08:10.000000000 -0500
***************
*** 363,372 ****
    }
    else {
      int i = luaL_checkint(L, 1);
!     if (i < 0) i = n + i;
!     else if (i > n) i = n;
!     luaL_argcheck(L, 1 <= i, 1, "index out of range");
!     return n - i;
    }
  }
  
--- 363,379 ----
    }
    else {
      int i = luaL_checkint(L, 1);
!     luaL_argcheck(L, (i <= -1) || (1 <= i), 1, "index out of range");
!     if (i < 0) {
!       if (i < -n) i = -n;
!       i = n + i + 1;
!       lua_settop(L, i);
!       return i - 1;
!     }
!     else {
!       if (i > n) i = n;
!       return n - i;
!     }
    }
  }