Description
Summary
The import Foo
syntax does two things:
- Resolves the name
Foo
using the containing Project's name=>uuid mapping and if necessary loads the package. - Creates a global binding
Foo
to assign the result to
In some instances it would be useful to be able to do the former without the latter. I propose the following new syntax:
let
local import Foo # Foo resolved in calling module's project
end
# No global identifier `Foo`
This only applies to import
(with all of its subforms), but not to using
.
Motivating use case
My motivating use case comes from Cedar, which has a string macro for defining SPICE circuits like so:
ckt = sp"""
* Inverter
Xneg VSS D Q VSS sg13_lv_nmos W=1.48u L=130n
Xpos VDD D Q VDD sg13_lv_pmos W=2.24u L=130n
.LIB "jlpkg://IHP_SG13G2/models/cornerMOSlv.lib" mos_tt
"""
In particular, the semantics of this string macro reference the IHP_SG13G2
package in the caller's project (so
the package resolution can't happen where the string macro is defined). Currently this lowers to import IHP_SG13G2 as $(gensym())
, which works, but pollutes the global namespace with an unnecessary binding.
I considered having the macro expand to an explicit call to Base.require
instead, but we have special support in the REPL that recognizes expanded import
s and offers to install the package, which is an important feature for this use case.
Frequently asked questions
Why is the local
keyword required?
We currently allow import
at global-but-local scope with global semantics:
julia> let
import Dates
end
julia> Dates
Dates
This is inconsistent with our treatment in fully local scope, where it is an error:
julia> foo() = import Dates
ERROR: syntax: "import" expression not at top level
Stacktrace:
[1] top-level scope
@ REPL[1]:1
but was added for convenience of expressions like @time using Foo
.
We could consider changing the scoping semantics in global-but-local scope in 2.0 and making the keyword optional.
What are the semantics with respect to expression ordering?
The import
effect is hoisted to top-level, i.e.
foo() = local import Dates
lowers to something like:
let #gensym# = require(:Dates)
foo() = local Dates = #gensym#
end
this is generally unobservable, but e.g. in a case where no Dates
package exists, you get an error even if you never call foo
.