I have a Lua project that uses some syntax sugar described in DecoratorsAndDocstrings, near the bottom of the page. It looks like this:

random =
  docstring[[Compute random number.]] ..
    return math.random(n)

Notice how the function definition is concatenated to the result of the function previous function call. I also have similar sugar like this:

something = foo.bar "Blah" { a = 1, b = 2 }

Basically, the idea is that instead of foo.bar taking two arguments (a string and a table), it only takes the first argument, and returns a function that finishes the operation (like a continuation). In the first example, instead of returning a function, we'd return a table with a __concat meta method.

I ended up with a handful of functions in my project that use syntax like this. The problem is:

  • The code is not straightforward.
  • It's difficult to document properly.
  • Adding support for non-sugared calls compounds the previous two problems.

So, I had the idea to just write them as normal functions, and create a "sweeten" function that takes two arguments: a function to "sweeten," and the number of args it wants. It returns a wrapper for the function. If you call the wrapper with at least the required number of arguments, it will simply tail-call the wrapped function. If you call it with less arguments, it will instead return a sort of "continuation table" with both __call and __concat metamethods set up as wrappers to the wrapper -- calling them will invoke the wrapper again with the previous args and the new args.

local function sweeten(fn, argc)
  local wrapper

  argc = tonumber(argc) or 2

  local function unpack_plus(t, i, ...)
    i = i or 1
    if i <= #t then
      return t[i], unpack_plus(t, i + 1, ...)
      return ...

  local function resume(t, ...)
    return wrapper(unpack_plus(t, 1, ...))

  local meta = { __call = resume, __concat = resume }

  wrapper = function(...)
    if select('#', ...) >= argc then
      return fn(...)
      return setmetatable({...}, meta)

  return wrapper

To test if out, you can do something like:

function foo(a, b, c) print(a, b, c) end
function sweet_foo = sweeten(foo, 3)
sweet_foo "qwe" "asd" "zxc" -- prints stuff

I feel like I should be able to simplify sweeten and still get the same result. I'd especially like to get rid of unpack_plus, but I can't see a way to do it. Any thoughts?


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.