lua-users home
lua-l archive

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


On 24 July 2016 at 23:48, Tim Hill <drtimhill@gmail.com> wrote:
>
>> On Jul 24, 2016, at 11:34 AM, Roberto Ierusalimschy <roberto@inf.puc-rio.br> wrote:
>>
>> Of course any function can return nil among its returns, but maybe
>> that might be considered a bad practice.
>>
>> My point is that, if nil is representing the absence of a value, it
>> is weird not to have a third value but to have a fourth one. If nil
>> is representing something else, probably it shouldn't be (as pointed
>> out by others already).
>>
>> ("is weird" ~ "it might be considered weird”)
>
>
> It’s weird *only* if the values are a strict list. If the values are (conceptually) a tuple, then of course an intermediate may be nil. While it could be argued that tuples should be returned as tables, you have the same problem; you can’t put a nil mid-way (and, as others have noted, returning a tuple directly can be faster and has less memory churn).

Yes, that's what I meant. In a function, sometimes I'll return three
unrelated things and any one of them might be nil.

function get_bla_data()
   -- ...
   return size, parent, color
end

-- parent might be nil, no problem
local size, parent, color = get_bla_data()

So in essence, I end up returning a tuple because that's what
multiple-returns (conceptually) are, but the order is pretty
arbitrary. I just want to return three things and get these three
things on the other side. Sure, I could return them as a table with
named keys, but that would be cumbersome/heavier/slower and then we
wouldn't need multiple-returns (or table-packing results!) at all.

function get_bla_data()
   -- ...
   return { size = size, parent = parent, color = color }
end

local bla_data = get_bla_data()
local size, parent, color = bla_data.size, bla_data.parent, bla_data.color
-- boring!!

Shocking thought right after writing this: if Lua had some kind of
table auto-destructuring syntax, could it get rid at once of varargs,
multiple-returns and table-packing-unpacking altogether!?

Thought experiment: here's a brave new blue-sky, half-baked "Lua 6 -
now everything-is-tables even more" world

(1) As a preliminary, needed for the stuff below, # becomes a special
valid table key (note that # ~= "#").

* #t is redefined to return t[#] if this key is set, or the Lua 5.3
semantics of #t if it is not.
* pairs(t) always skips t[#]
* if t[#] is set, ipairs(t) traverses up to t[#]

(2) Multiple returns are now actually a table

   return x, y, z

becomes sugar for

   return { [1] = x, [2] = y, [3] = z, [#] = 3 }

(3.1) Function arguments and varargs are now actually a table

   function f(x, y, z)
     -- ...
   end

becomes sugar for

   function f(_ARG)
      local x, y, z = _ARG[1], _ARG[2], _ARG[3]
      -- ...
   end

(3.2) Explicit varargs become a destructuring construct for _ARG:

   function(x, y, ...)
      local t = { ... }
   end

becomes sugar for

   function f(_ARG)
      local x, y, z = _ARG[1], _ARG[2]
      local t = { [#] = _ARG[#] - 2, [1] = _ARG[3], --[[...]] }
   end

(4) Function calls auto-structure its arguments into a table:

   f(10, 20)

becomes sugar for

   f({[1] = 10, [2] = 20, [#] = 2})

(5.1) Returns of function calls in expression contexts
auto-destructure the return table:

   local x, y, z = f()

becomes sugar for

   local _t = f()
   local x, y, z = _t[1], _t[2], _t[3]

(5.2) Returns of function calls in table constructor contexts
auto-destructure the table, including the size:

   local t = { f() }

becomes sugar for

   local _t = f()
   local t = { [1] = _t[1], [2] = _t[2], [3] = _t[3], [#] = _t[#] }

(translation of { f(), g() } with reasonable semantics and the
equivalent for (5.1) are left as an exercise (I said it was
half-baked!) )

Results:

* All functions receive a single argument, a table (_ARG), and return
a single argument, a table (which you never get to touch).

* No more table.pack() or select(), just use _ARG explicitly.

* No more table.unpack(), just do { f() }, which does the right thing

* { ... } also does the right thing

Of course, the implementation would optimize away these implicit
tables at compile-time. After all, the vast majority of functions
won't refer to _ARG explicitly and the return-values table is
essentially conceptual. Only functions using _ARG would pay for a
table, etc.

I hope you all enjoyed reading this as much as I enjoyed writing it,
and now have fun shooting it down by pointing out all the obvious
flaws that I evidently haven't spent any time pondering about! :-D

-- Hisham