Skip to content

Async functions with arguments do not work (parameters are ignored) #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
luiz00martins opened this issue Dec 19, 2022 · 5 comments

Comments

@luiz00martins
Copy link

Simple example:

local do_thing = a.sync(function (val)
  local o = 10
  return o + val
end)

print(do_thing(5))

This should print 15, but instead it results in an error at line 3: attempt to perform arithmetic on a nil value (local 'val').

It seems like the library didn't pass the value 5 to the function.

@luiz00martins
Copy link
Author

Even in the preview from the README, it's not clear to me how parameters should work. For example:

local a = require "async"

local do_thing = a.sync(function (val)
  local o = a.wait(async_func())
  return o + val
end)

local main = a.sync(function ()
  local thing = a.wait(do_thing()) -- composable!

  local x = a.wait(async_func())
  local y, z = a.wait_all{async_func(), async_func()}
end)

main()

The function do_thing takes a parameter val. However, the function is called without it, with do_thing(). So, how is the parameter supposed to be used?

@luiz00martins
Copy link
Author

Found a solution:

local async = function (fn)
	return function (...)
		local args = {...}

		return function (cb)
			assert(type(fn) == "function", "type error :: expected func")
			local thread = co.create(fn)
			local step = nil
			step = function (...)
				local stat, ret = co.resume(thread, ...)
				assert(stat, ret)
				if co.status(thread) == "dead" then
					return (cb or function (r) return r end)(ret) -- Added a return
				else
					assert(type(ret) == "function", "type error :: expected func")
					return ret(step) -- Added a return
				end
			end
			return step(table.unpack(args)) -- We pass the arguments here
		end
	end
end

The main disadvantage, is that now "bare" async functions have to be called two times to startup:

local async_fn = a.sync(function(a,b,c)
	print(a,b,c)
end)

local async_fn2 = a.sync(function()
	await(async_fn(1,2,3))
	await(async_fn(4,5,6))
	await(async_fn(7,8,9))
end)

async_fn2()() -- Note the double call

-- Prints:
-- "
-- 1 2 3
-- 4 5 6
-- 7 8 9
-- "
local do_thing = a.sync(function (val)
  local o = 10
  return o + val
end)

print(do_thing(5)()) -- Note the double call

-- Prints "15"

@timrockefeller
Copy link

I made another approach with little change to pong.

Usage:

local task = function(...) -- define task function body first
  print(...)
  --or some `a.wait` here
end

local a_task = a.sync(task, "params", 233) -- build the thunk (pong) with params
a_task(<callback func>) -- run
--- OUTPUT
--  params 233

Modifies:

local pong = function (func, ...)   -- `wrap` unpacked the params from sync(...), so we need pickout real `<callback func>` from them

  assert(type(func) == "function", "type error :: expected func")
  
  -- do the pick
  local params = {...}
  local callback = table.remove(params)

  local thread = co.create(func)
  local step = nil
  step = function (...)
    local stat, ret = co.resume(thread, ...)
    assert(stat, ret)
    if co.status(thread) == "dead" then
      (callback or function () end)(ret)
    else
      assert(type(ret) == "function", "type error :: expected func")
      ret(step)
    end
  end
  step(table.unpack(params))   -- params here for coroutine entrance
end

@luiz00martins
Copy link
Author

luiz00martins commented Feb 7, 2023

@timrockefeller I get an error trying to call it:

local task = function(...) -- define task function body first
  print(...)
  --or some `a.wait` here
end

local a_task = a.sync(task, "params", 233) -- build the thunk (pong) with params
a_task()

This yields:

lua: test.lua:23: attempt to call a number value
stack traceback:
        test.lua:23: in local 'step'
        test.lua:29: in function <test.lua:9>
        (...tail calls...)
        test.lua:94: in main chunk
        [C]: in ?

However, you can fix that by adding a nil check to wrap:

local wrap = function (func)
  assert(type(func) == "function", "type error :: expected func")
  local factory = function (...)
    local params = {...}
    local thunk = function (step)
      if not step then -- check nil here
        step = function (...) end
      end
      table.insert(params, step)
      return func(table.unpack(params))
    end
    return thunk
  end
  return factory
end

Still, await doesn't seem to work as expected. Running:

local f = a.sync(function(arg1, arg2, arg3)
	print('arg1: ' .. arg1)
	print('arg2: ' .. arg2)
	print('arg3: ' .. arg3)
end)

local g = async(function()
	a.wait(f('arg1', 'arg2', 'arg3'))
	a.wait(f('arg1', 'arg2', 'arg3'))
	a.wait(f('arg1', 'arg2', 'arg3'))
end)

g()

Yields an error: lua: attempt to concatenate a nil value (local 'arg1')

@MarcWeber
Copy link

I fixed it by switching to luajls See #10.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants