lua-users home
lua-l archive

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


On 07/26/2016 12:19 AM, Hisham wrote:
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

Actually, I was thinking in the same direction. I would also like to have a special key # that represents the length of a "fixed" size sequence. I actually would rather called it 'explicit' size, because it isn't fixed at all, it just doesn't adapt itself to its content.

What I thought was that I need a good syntax for simple sequence table constructors as well. e.g t = {x, y, z}

First try
{ x, y, z, # }
is syntactic sugar for

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

A simply # in the constructor sets t[#] at the current length (t[#] = #t). I am not sure about it though, as # acts here as an operator and not
as a value.

Or maybe this would also be nice:
{ (x, y, z) }

Now if I combine this with my ideas about more efficient call by name functions calls, it would make a nice power patch. :-)

--
Thomas