lua-users home
lua-l archive

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


On 2023-05-13 00:39, Pierre Chapuis wrote:
Anyway, far from me the idea to turn Lua into Python. I just thought
for once this proposal kind of fits the "mechanisms not policies" and
would be a good way to add type annotations to Lua *if* it gets some.

Apropos mechanisms: I've often wished for some general annotation / metadata mechanism, basically "an extra slot in every value" plus some API / syntax to access it. Could be used for types, but also doc strings, profiling information, or whatever else you might need.

Generally, it's possible to invert this and instead use a big metadata table indexed by values. (While special syntax might be nice-to-have, it isn't actually all that important)… Except, unfortunately, closures are a problem: In the case of

    function newCounter( )
      local i = 0
      return function( )  i = i + 1 ; return i  end
    end

every instance of the inner function has a new identity, and you currently can't associate some piece of metadata with *all* of them, at least not without adding relatively a lot of code and a manual association mechanism.

Having a `debug.protoid( f )` (returning a unique identifier [i.e. the pointer] for the function's prototype, analogous to `debug.upvalueid( f, n )` for the upvalue slots) would be a great tool, since then you can actually associate the metadata with the function's prototype (that's shared between all its instances / closures) without manually faking it.

With that, it's actually not too hard to (ab)use metamethods etc. to add annotations to values that can then at the very least be used by e.g. testing code, or (for docstrings) a fancy REPL.

* * *

Beyond that, a general annotation syntax that's allowed in weird places (e.g. valid both as a statement and as part of an expression, and also allowing e.g. `function () :: foo ... end` for `(function() ... end) :: foo`, where `::` is the placeholder for whatever that thing ends up being), that either defaults to doing nothing or just throwing an error, and equipped with a metamethod for adding actual semantics, would allow pretty comfortable usage… but this second part is pretty optional (and way more complex than the first bit). With such a syntax, you can write something like

    function newCounter( ) :: fun(nil,  fun(nil,  number))
      local i = 0 :: number
      return function( ) :: fun(nil,  number)
        i = i + 1
        return i
      end
    end

and if the metatable of whatever `fun` & `number` are has an appropriate entry for `::` you get the type info associated / saved for the values, such that later introspection can access & use it. Or if you want doc strings…

    function newCounter( ) :: [[
      blah blub
      ]]
      return function( ) :: "blop beep"  i = i + 1 ; return i  end
    end

…looks quite readable. But following the definition with normal function calls for annotations…

    function newCounter( )
      return function( )  i = i + 1 ; return i  end
    end
    doc( newCounter ) [[
    blah blub
    ]]
    -- that's making an instance of the inner closure to then extract its prototype to attach info…
    doc( newCounter( ) ) [[
    blop beep
    ]]

isn't actually that much worse, and *way* less complicated. (So fancy syntax most likely wouldn't be worth it for me, but looking at whether something like `debug.protoid` (or maybe an actual debug/doc/whatever slot in every value[1]) could/should be added might be worth it?)

-- nobody

[1] Even if you have to flip a flag in luaconf.h for an actual slot to exist, a "blessed" / unified interface that people can coordinate around means collaboration is a lot easier than when everyone brews their own annotation / metadata mechanism.