Or rather, the definition happens once (per module instantiated), but the side effects can happen twice: once on compilation, and again on first import. This is because the compiler doesn't cache the modules it creates. It wasn't really noticeable until I tried doing some REPL-driven tkinter, when I noticed that a recompile made a new window pop up, even when guarded by a defonce.
Furthermore, every recompilation will trigger the side effects again, even when they're guarded by a defonce. This is because the compiler doesn't read the cache either. Maybe it should. Caching is why imports don't.
When I wrote the compiler to begin with, I didn't have a defonce macro. I was also thinking about workflows where the compiler was run in a different session than the import (maybe even on a different version of Python). I still want to support that, but I also want to support the REPL-driven workflows where a defonce would matter, and the current behavior doesn't seem right for that.
reload used to be a builtin, but subtleties around globals sometimes not getting deleted made it too confusing for beginners, so it got moved. Clojure has similar problems, but the faster feedback loops are valuable enough to make the REPL-driven style worth learning. Making the compiler use the cache would have similar issues.
I'm tempted to put in a kwarg to keep the old behavior, but it's probably not necessary. One can always delete the module from the cache, but one also has to know that's possible.
The desired caching behavior makes me wonder if the compiler should be an import hook. I still kind of think it shouldn't. Sometimes the source isn't from a file. Even when it is, we still want to produce .py files, and macros mean the programmer needs to be able to control when compilation happens.
Or rather, the definition happens once (per module instantiated), but the side effects can happen twice: once on compilation, and again on first import. This is because the compiler doesn't cache the modules it creates. It wasn't really noticeable until I tried doing some REPL-driven
tkinter
, when I noticed that a recompile made a new window pop up, even when guarded by adefonce
.Furthermore, every recompilation will trigger the side effects again, even when they're guarded by a
defonce
. This is because the compiler doesn't read the cache either. Maybe it should. Caching is why imports don't.When I wrote the compiler to begin with, I didn't have a
defonce
macro. I was also thinking about workflows where the compiler was run in a different session than the import (maybe even on a different version of Python). I still want to support that, but I also want to support the REPL-driven workflows where adefonce
would matter, and the current behavior doesn't seem right for that.reload
used to be a builtin, but subtleties around globals sometimes not getting deleted made it too confusing for beginners, so it got moved. Clojure has similar problems, but the faster feedback loops are valuable enough to make the REPL-driven style worth learning. Making the compiler use the cache would have similar issues.I'm tempted to put in a kwarg to keep the old behavior, but it's probably not necessary. One can always delete the module from the cache, but one also has to know that's possible.
The desired caching behavior makes me wonder if the compiler should be an import hook. I still kind of think it shouldn't. Sometimes the source isn't from a file. Even when it is, we still want to produce .py files, and macros mean the programmer needs to be able to control when compilation happens.