0

The following do/a macro can auto-insert await when asyncio function is used. The following also shows usage.

(import asyncio)
(import time)

(defmacro do/a [&rest code] 
  `(do ~@(lfor p code
                (if
                  (= (cut (str (first p)) -2) "/a")
                  `(await ~p)
                  p))))

(defmacro progn/a [&rest code]
  `(.run_until_complete (.get-event-loop asyncio )
     ((fn/a []
        (do/a ~@code)
        ))
     ))

(defn/a sleep_test/a [t]  
  (await (asyncio.sleep t))
  (print t)
  t)

(defn sleep_test [t]  
  (time.sleep t)
  (print t)
  t)

(progn/a
  (print 3)
  (await (sleep_test/a 3))
  (sleep_test/a 2) ;;can omit await
  (sleep_test 1) ;;auto swich by fn name 
  (+ 20 30)
  )

This macro detects async function by the function name "/a". It is better using asyncio.iscoroutinefunction to detect async functions. But this does not work. Plz see the following macro and executed result.

(defmacro isasynctestmac [f]
  (if (asyncio.iscoroutinefunction f)
      `["async"  ~(asyncio.iscoroutinefunction f) (asyncio.iscoroutinefunction ~f) (type ~f)] 
      `["not async" ~(asyncio.iscoroutinefunction f) (asyncio.iscoroutinefunction ~f) (type ~f)] 
          ))

(isasynctestmac sleep_test/a)

==> ['not async', False, True, <class 'function'>]

You will see an async function is regarded as a symbol in hy-lang macro. Appling eval can not avoid this problem.

How to resolve this problem?

niitsuma
  • 117
  • 1
  • 4

2 Answers2

1

Macros run at compile time, and whether a variable holds a coroutine is only known at run time, so iscoroutinefunction needs to be called at run time. (asyncio.iscoroutinefunction f) in your macro isasynctestmac merely checks the symbol that's used as the variable name, not the value of the variable. Here's how you could write do/a with iscoroutinefunction, and here's the rest of the code with e.g. the extra parenthesis in sleep_test removed. (In the future, make sure the parts of your code that are supposed to already work really do already work before posting to Stack Overflow.)

(import asyncio time) 

(defmacro do/a [&rest code] 
  `(do ~@(lfor p code
    (if (and (instance? HyExpression p) p (!= (first p) (HySymbol "await")))
      `(if (asyncio.iscoroutinefunction ~(first p))
        (await ~p)
        ~p)
      p))))

(defmacro progn/a [&rest code]
  `(.run_until_complete (.get-event-loop asyncio)
    ((fn/a []
      (do/a ~@code)))))

(defn/a sleep_test/a [t]  
  (await (asyncio.sleep t))
  (print t)
  t)

(defn sleep_test [t]  
  (time.sleep t)
  (print t)
  t)

(print (progn/a
  (print 3)
  (await (sleep_test/a 3))
  (sleep_test/a 2)
  (sleep_test 1)
  (+ 20 30)))
Kodiologist
  • 2,984
  • 18
  • 33
  • This approach causes error when write await in non-async code. `(defn sleep_test [t hintfn] (if (asyncio.iscoroutinefunction hintfn) (await (asyncio.sleep t)) (time.sleep t))` => `hy.lex.exceptions.PrematureEndOfInput: Premature end of input` . Then I use function name for distinguishing async context in my lib https://pypi.org/project/gasync/ – niitsuma Feb 04 '20 at 04:13
  • @niitsuma `PrematureEndOfInput` is a syntax error. It's telling you that you forgot a closing parenthesis. – Kodiologist Feb 04 '20 at 12:38
0

The approach @Kodiologist show has the following problem. The following python code works.

import asyncio
import time
async def sleep_testa(t, hintfn):
    await asyncio.sleep(t) if asyncio.iscoroutinefunction(hintfn) else time.sleep(t)

But after remove async before def, the program stop with error SyntaxError: invalid syntax

import asyncio
import time
def sleep_testa(t, hintfn):
    await asyncio.sleep(t) if asyncio.iscoroutinefunction(hintfn) else time.sleep(t)
==> SyntaxError: invalid syntax

It seems to impossible dynamically switching async and non-async code. We must switch async and non-async code before macro expand.

niitsuma
  • 117
  • 1
  • 4