lua-users home
lua-l archive

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


Hi Lua lovers!

Here's an idea and i hope we can have some kind of lazyness in Lua.


The proposed syntax and semantics follows:

1) Lazy variables are kind of local variables. Though, their value is not calculated until it actually needed, after that they start acting as usual local variables. So, some_function() will not be called until you use the variable l value:

lazy l = some_function(arg1, arg2)-- some_function() not called yet

-- some_function() called and result is used, l starts to be usual var
local l_twice = 2 * l
-- some_function() not called, previously calculated result is used
local l_thrice = 3 * l



**) Not clear:


-- should other_function() call be deferred too?
lazy l2 = (function() do_hard_calculations(arg1) +
                      do_hard_calculations(arg2) end)(a, other_function(b))


Should the call other_function(b) be treated as lazy too? To simplify the implementation i think it should _not_ be lazy, one who want it be lazy should split it to declaring some other lazy variable assigning it this call, and the use it in above declaration, like:

-- other_function() and do_hard_calculations() will not be called yet
lazy tmp2 = other_function(b)
lazy l2 = (function() do_hard_calculations(arg1) + do_hard_calculations(arg2) end)(a, tmp2)



2) If one doesn't read a value from variable but just set new value - it should start acting like a regular local variable, no calculations done. So in the example, if you assign something to l the some_function() will not be called:

lazy l = some_function(arg1, arg2) -- some_function() not called yet
-- some_function() not called and all this lazy stuff is garbage collected
l = "will not call any lazy calculations stored in l before"

3) Lazy table is a table with lazy values. So on first __index by some key, this value is calculated and start behave as an usual value. If value has never been read before __newindex by this key - it just doesn't get calculated and new value just should have been set. So, very_consuming_function() will not be called until you use the t.wow value, and if you assign something to t.wow2 the very_consuming_function2() will not be called.

lazy t = {
    "wow" = very_consuming_function(),
    "wow2" = very_consuming_function2(),
}
-- call very_consuming_function() and assing result to t.wow2, do not call very_consuming_function2()
t.wow2 = t.wow

Side note: actually the simplest implementation should just treat lazy table above to be the same as just having lazy intermediate values stored in the table:

lazy __tmp_v1 = very_consuming_function()
lazy __tmp_v2 = very_consuming_function2()
lazy t = {
    "wow" = __tmp_v1,
    "wow2" = __tmp_v2,

}


4) Lazy table keys - not sure about lazy keys - it might have a sense to have lazy keys until the first __index or __newindex performed on whole table, because keys' nature demand that after the first __index or __newindex on table it should have all keys be known. Implementation of lazy keys may add unneeded complexity so maybe the gamble is not worth the candles, as there's not much cases when it will be really useful, but whatever:

lazy t = {
    [consuming_function(arg)] = other_consuming_function(argx), -- keys laziness: do we need it? if answer is yes - consuming_function(arg) will be only called on a first __index or __newindex on this table
}

5) Function arguments and return passing should be revised regarding this. The whole thing can have a clear benefits from lazyness (though, it may have some semantic glitches to resolve, but again, simplest implementation probably will not need to do something special in this case, just pass variables as is):

function some_func(arg)
   lazy db_result = db.do_query(my_query, arg)
   return db_result
end

local b = some_func(5) -- i here some_func() is called but db.do_query() is not
-- here goes the actual db.do_query(my_query) performed and the result is passed to do_somehting()
do_somehting(b)

lazy l1, l2 = some_func(4), some_func(3)
function func(a, b)
   if len(a) > 5 then
      do_somehting(b)
   end
end

-- here some_func(4) is called but if it returns less than 6 results, some_func(3) will not be called
func(l1, l2)

6) The function varargs should be revised regarding this as they may benefit from lazy varargs. It can be even more complex to implement, but hopefully there's some elegant way doing this, who knows.
 Consider this tricky code:

   function i_return_many_values(...)
      args = {...} -- or  lazy
args = {...}
      return calc1(args[1]), calc2(args[2]), calc3(args[3])
   end

   lazy st = { other_calc(v1), other_calc(v2), other_calc(v3), other_calc(v4) }
   lazy rt = { i_return_many_values(unpack(st)) }

Here we may define how lazines will behave here and whether we do support special cases and how, we'll consider what's essential and what isn't, just to have the simplest implementation for semantics

Disclaimer:
I remember that Lua will not tradeoff feature for extra complexity and size of its source code, though, with this lazy thing i'm just hoping that there's some decent way to achieve this without adding a lot of complex code, so i don't expect it to be some true feature-rich laziness or so. My apologizes if it will have no interest for community


So, to open the discussion - is there any chance to implement something like this in Lua? There's a kind of problems in real life which will natively benefit from this (and i have faced one - that's why i've started thinking about it several months ago). Actually, it will be kind of 'user controlled laziness' as opposed to 'full-fledged always-be laziness' in Haskell (and afaik people in some cases are struggling the laziness in Haskell when some action or calculation should be performed now, in non-lazy way)


I don't have any deep confidence with the Lua source code and VM, though, but still have some idea. Lazy variables can be implemented as some special type which initially have value of anonymous function which contains the actual code for calculations wrapped in it. Thus it will need support in parser/lexer as well - parser should take care of lazy declarations and create proper bytecode. Then on first use VM should take this value (actually anonymous function), execute it and assign the result back to variable. Lazy tables just then will need only lazy values, not lazy keys (a lot of complexity will come with lazy keys) - and thus with some support from parser/lexer we will have it out of the box without inventing special type for lazy tables. Arguments passing then will be just passing values of this lazy type, so no special work on it too. Just a bare thoughts, don't blame me if i've missed everything.


Sincerely,
  Volodymyr