[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Negative index in calls to select()
- From: Doug Rogers <rogers@...>
- Date: Fri, 09 Jan 2009 17:48:58 -0500
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;
! }
}
}