Add wildcard imports#183
Conversation
| When the library module is called `prelude`, it is a module designed for wildcards. Adding a new item to it is a semver breaking change. | ||
| Wildcard importing from it is always allowed. (When we add exports, we will make this controllable instead of only giving special treatment to the `prelude`.) | ||
|
|
||
| Other library modules are not designed for wildcards. If another wildcard import exists, then an `wildcard_import` [diagnostic](https://www.w3.org/TR/WGSL/#diagnostics) at the error level will be emitted. |
There was a problem hiding this comment.
Note that this is not a bulletproof mechanism. One can still construct cases such as
// Allowed:
// This isn't a module designed for wildcard importing,
// but I don't have any other wildcard imports.
import bevy::math::*;
const a: f32 = 3;
Then, if bevy::math adds struct f32 { a: u32 }, it would be a breaking change.
Wildcards do shadow builtins after all. The builtin shadowing is a necessity, because we don't want future WGSL evolutions to randomly break existing WESL code.
There was a problem hiding this comment.
Interesting point. Anything we should do now? I imagine the WGSL folks won't want to be limited in the future use of their reserved words.
6232981 to
0156aa9
Compare
|
This is very confusing to me. There was an ongoing discussion in issues. The hackmd was neither mentioned on discord nor in the issue. So it seems to me, that there is an internal design team consisting of two people that decides on this. This pr has not been merged yet, but it is weird to me, that the discussion switched to the hackmd. (Bevy uses hackmd too, but it often gets referenced in issues, discussions or on discord.) It states that library authors don't expect adding new items to be a breaking change. But the issue discusses this. This premise does not seem as clear as it is stated here. Most people find this in rust to be a non issue in that discussion. The whole "private vs public by default" discussion seems decided here. It mostly took the original proposal in the discussion from the author and renamed ambient to prelude. This still seems to me like a "magic keyword" that would confuse me a lot as a library author and user. Especially since bevy uses it subtly different. I couldn't find anything about this, but this PR raised the following question for me: The PRs here might just be there for lose discussion, but it assumes some things on such a fundamental level that I'm not sure it is that. (Like private by default) |
|
@Arne-Berner Most of the discussions happened on the WESL Discord servers or internally, since that is a fast way to get feedback on ideas. The hackmd note are thoughts that I wrote down after that. With that, this proposal here is one of the few designs that I'm reasonably confident in. That said, this PR here is for discussion before I push through something unreasonable. We are leaning towards having private by default. Could you point out the place where this proposal assumes that? If it does, then that is likely an oversight on my part. Regarding
The cargo documentation explicitly calls out this corner case, since it is unexpected. One of our goals for WESL is to allow for safe library evolution beyond the Rust ecosystem. This means taking into account what npm users expect of semver, and also designing a safe path for future languages that we'll add. Semver breaking changes depend on how and when the package manager will do patch upgrades.
This really wasn't my intent. My intent was to surface the corner cases via diagnostics. The Though, how does Bevy's different usage look like? |
I'm sorry for not being clear there. When I said "public by default vs private by default" I was talking about using "export" on a module, to make it available for users.
Bevy uses prelude as a marker for utilities that are most likely needed. It is a helper for the "most important functions". Naming a crate prelude does not have any function, besides being a convenient function. I must have misunderstood
English is not my first language. If you wrote "See also ... for my prior thoughts on this" I wouldn't have assumed that the hackmd was the official discussion threat that lead to this decision. Getting all subtleties in english sometimes is hard for me. I've been lurking in the discord and must have missed further discussion about this topic. |
|
Hi @Arne-Berner our project is younger and smaller than Bevy and we haven't polished a procedure about where we discuss. We're still small enough that we sometimes save issues for live meetings, rather than doing things async. Undoubtedly we'll grow more standard over time, especially if more folks want to engage! Here Stefan is trying to recall/refresh our earlier discussions, I'm sure we'll discuss/modify further before the final form lands in the spec. We aim for unanimity among the three core committers before we commit spec changes. |
|
@Arne-Berner First to answer this:
We always submit ideas to the community in the shape of Discord discussions, GitHub issues and GitHub PRs (in that order) before merging. So far the "leadership" has been rather natural, since there are three main contributors (@stefnotch, @mighdoll and me) and reaching consensus has always been possible. Though, since you're raising a valid concern, I'll open a separate issue for further discussion and link it here. EDIT: #184 Back on topic I was strongly in favor of unrestricted wildcards. As you said, they are usually not an issue. But the proposal written by @stefnotch may give you the wrong idea, at a first glance, because it goes deep into the edge cases. To clarify this PR:
In other words, a large majority of users would just never meet these edge cases. And if they do, it's a compile-time diagnostic that can be silenced. |
| ### Wildcard imports from libraries | ||
|
|
||
| For libraries, we distinguish between modules that were designed for wildcard imports and modules that were not. | ||
| Library authors generally expect that adding a new item is not a breaking change. Wildcard imports are a surprising edge case. |
There was a problem hiding this comment.
We should add quite a few links here to back this up. Wildcard imports are a brittle feature in many programming languages.
There was a problem hiding this comment.
I think linking the decisions about wildcards in the most common webgpu languages should suffice. Rust allows it, javascript disallows it, c++ uses "using namespace std;" but discourages it.
There was a problem hiding this comment.
I should also look through the discussion around the clippy lint for wildcard imports. While that lint is a lot more heavy handed, it might contain valuable information.
| TODO: Or should it immediately result in a warning, even if the variables are unused? | ||
|
|
||
| ### Wildcard imports from libraries | ||
|
|
There was a problem hiding this comment.
This should probably start by saying something like "The following section applies when the current module has two or more wildcard imports. We specifically deal with the potential name clashes and corner cases. Users are not expected to run into this edge case. It exists to give us certain guarantees in the language itself. This will be useful, for example, to build semver checking tools."
There was a problem hiding this comment.
I don't see any mention of this "two wilcards rule" any more. Is this comment outdated?
|
|
||
| WESL detects the edge cases and will emit a diagnostic. This makes upholding semver guarantees significantly easier. | ||
|
|
||
| When the library module is called `prelude`, it is a module designed for wildcards. Adding a new item to it is a semver breaking change. |
There was a problem hiding this comment.
Should we make the prelude special? Or should we find some other way of making modules as "safe for wildcard imports even if there are multiple of them"
There was a problem hiding this comment.
I think the issue discusses this at length:
- Library authors could think their library should be imported without restriction and everything gets put into prelude.
- it's hard to understand why sometimes multiple imports work (using prelude) and sometimes it needs diagnostic opt in
- it's changing the definition of semver as most people understand it
There was a problem hiding this comment.
Edit: see my next comment first
I do not think prelude should be special. Implicit hard-coding of this feature to a specific module name is both implicit (hard for authors and consumers to understand and track), and overly restrictive (prevents library authors from defining the interfaces they want).
Given that the competition in the shader space generally looks like this:
import shader_material;
I suspect there will be significant appetite for "flat" scoped modules intended to be pulled in wholesale for a context:
import shader_material::*;
Forcing library authors to define a redundant (and longer) import_shader_material::prelude::*, then forcing library consumers to choose between import shader_material::Symbol and import shader_material::prelude::Symbol is "friction" / confusion on both sides.
In a world where wildcards aren't supported everywhere by default (which is not the world I think we should be living in), library authors should be able to opt-in their modules at the language level. This provides explicit, positive handoff (rather than implicit special casing), and flexibility / autonomy on both sides. Something like:
export wildcard shader_material;
There was a problem hiding this comment.
Hmm either I misread or I was reviewing an older version:
(When we add exports, we will make this controllable instead of only giving special treatment to the
prelude.)
I'll take back my concerns here.
No, that was very clear! Also I personally really like the proposal (except for the prelude addition). Probably that prelude decision was throwing me off track the most since there seemed to be some sound arguments in the issue about it. I also think public vs private by default would change this proposal a lot. But that might just be me. Using opt in diagnostics as a user feels very clever to me :) |
|
|
||
| Wildcard imports are used to import a whole set of items into the current module. They are lower priority than local names and explicit imports to reduce the chance of breaking changes if the wildcard-imported module changes. Wildcard imported modules are eagerly loaded, since they need to be checked before accessing any predeclared items. | ||
|
|
||
| When multiple wildcard imports are used, name clashes are possible. |
There was a problem hiding this comment.
I recall you discovered a particularly bad case if library zap wildcard imports foo and bar. A js package manager can follow semver and create a tough user problem if zap now has a wildcard-wildcard conflict. Disallow double external wildcards entirely for now? Or restrict to publisher designated wildcard-safe modules like prelude?
There was a problem hiding this comment.
As far as I know most services freeze their dependencies and only update them in a safe environment. This would be an easy fix, since you just need to import the specific item you care about explicitly. But there seem to be two sides to this argument:
Some people from the rust side seem to say "it hasn't been an issue in rust"
And you rightfully say that "it might be an issue here"
We can't know that but I think it should cater to the needs of the people that are willing to use it now and not exclusively to a future audience.
There was a problem hiding this comment.
We embed our WESL enhanced WebGPU libraries in both npm and cargo, cross platform shader libraries! But much of the npm ecosystem uses semver by default (it's the default if you npm install). Their expectation is that well behaved packages will not fail on patch/minor updates. We can't hope that npm users will change their stripes, it's on us to make sure that npm update basically never fails for well behaved WESL libraries.
There was a problem hiding this comment.
This is not considered a major change because conventionally glob imports are a known forwards-compatibility hazard. Glob imports of items from external crates should be avoided.
The cargo documentation warns against glob imports. So even if it does not come up too often in Rust, doing glob imports is still a discouraged pattern according to the official guidelines.
| When the library module is called `prelude`, it is a module designed for wildcards. Adding a new item to it is a semver breaking change. | ||
| Wildcard importing from it is always allowed. (When we add exports, we will make this controllable instead of only giving special treatment to the `prelude`.) | ||
|
|
||
| Other library modules are not designed for wildcards. If another wildcard import exists, then an `wildcard_import` [diagnostic](https://www.w3.org/TR/WGSL/#diagnostics) at the error level will be emitted. |
There was a problem hiding this comment.
Interesting point. Anything we should do now? I imagine the WGSL folks won't want to be limited in the future use of their reserved words.
| import bar::*; // exports clashing_name | ||
| ``` | ||
|
|
||
| The name clash is ignored until the item is used. |
There was a problem hiding this comment.
I should also clarify that name clashes only happen when the imported items are different. It is totally possible to wildcard import the same thing.
|
I don't think stopping people from using glob imports makes sense beyond giving a configurable diagnostic. Triggering special behavior on using 2 glob imports also seems weird if the collision can already occur with only 1 (if a library adds a function that shadows a builtin) |
| The name clash is ignored until the item is used. | ||
| TODO: Or should it immediately result in a warning, even if the variables are unused? | ||
|
|
||
| ### Wildcard imports from libraries |
There was a problem hiding this comment.
I find this current proposal slightly more agreeable than the previous one:
- It uses existing public/private semantics / keywords rather than introducing new ones (or at least, it doesn't explicitly define new things in this space, so I'm assuming this)
- It prescribes
prelude(has existing uses in the wider language ecosystem) for the "wildcard" module name instead ofambient(a brand new word for this space). Given that in Bevy we want to call our wildcard modulesprelude, this is nice. - It gives users autonomy. They can wildcard import anything provided they suppress the diagnostic.
I still have concerns, but I'll create separate threads for them / respond to existing ones, so we can keep this organized:
Major concerns that I would personally block on:
I strongly disagree that. Looks like I either misread or was reviewing an older version. The current wording states that this will ultimately be configurable.preludeshould be special-cased here / be the only module that supports this
Minor concerns:
- I think wildcard imports on modules that aren't flagged as wildcard importable should emit warnings, not errors.
- I still think this approach introduces language complexity and user-facing pain / friction for nearly intangible benefits. I won't create a thread for this as I've already said my piece in the issue, and we're still here.
There was a problem hiding this comment.
I do think this proposal only really makes sense in the context of "private by default". Otherwise a bunch of new concerns are introduced (which I discussed in the issue).
| When the library module is called `prelude`, it is a module designed for wildcards. Adding a new item to it is a semver breaking change. | ||
| Wildcard importing from it is always allowed. (When we add exports, we will make this controllable instead of only giving special treatment to the `prelude`.) | ||
|
|
||
| Other library modules are not designed for wildcards. If another wildcard import exists, then an `wildcard_import` [diagnostic](https://www.w3.org/TR/WGSL/#diagnostics) at the error level will be emitted. |
There was a problem hiding this comment.
Given that code can still compile with this, and it only protects against potential / theoretical future breaking changes, I think it should be a warning. This feels like a "best practices enforcement thing" (akin to using the wrong naming conventions). Why break peoples' development flow for it?
|
Thanks @cart for clarifying which issues seem blocking for getting going with bevy usage/users. The language design issues tend to interrelate, and I would like to help make sure things are unblocked for when bevy is ready to use WESL. On whether occasional library semver breakage is an acceptable cost for language simplicity wrt to wildcards, can you see the perspective of non-rust communities? Many language communities have chosen more restrictions rather than relying solely on community convention + occasional client cleanup to achieve safe wildcard use. I don't think it's fair to say that other communities reasoning is intangible or only theoretical. It's just a different choice on a spectrum of choices balancing stability vs user flexibility. In practice for WebGPU/WESL, the js community expects semver to work and we embed in their library ecosystem. So we have to educate every new js user coming over to WESL and WebGPU libraries on rules for using wildcards safely. I want to use our linker/linter/ide tools to guide them in the right direction. Personally, I managed engineering teams using unrestricted wildcards fruitfully for many years in Scala. I like using them just fine, even though I think one of these middle ground solutions is better for the WebGPU/WESL community. If we can fully deliver cross platform WebGPU shader libraries, we'll have something very valuable for the community. That's a prize I like to keep an eye on. I don't like to compromise on simplicity goals, but I'm willing to do it here if it helps us get to compatible libraries. |
|
I've pushed a new version of the proposal, but now that I've written out the longer diagnostics option, I'm not entirely convinced that it is the best solution. The part about glob imports in libraries causing issues is preventable by making the globs explicit upon publishing. As in, if then when publishing In user code, I don't think I care too much about a glob import that has just broken. It is an easy fix for me. The part about accidentally shadowing a builtin seems impossible to fully prevent without #185 |
Arne-Berner
left a comment
There was a problem hiding this comment.
Using prelude still seems out of place in this context and very restricting to the naming scheme of library authors.
|
|
||
| 2. Emit a warning [diagnostic](https://www.w3.org/TR/WGSL/#diagnostics) named `wildcard_import` if there are multiple wildcard imports from libraries and at least one is not designed for wildcards. | ||
|
|
||
| For this, we distinguish between modules that were designed for wildcard imports and modules that were not. When the library module is called `prelude`, it is a module designed for wildcards. Adding a new item to it is a semver breaking change. Therefore, wildcard importing from it is always allowed. (When we add exports, we will make this controllable instead of only giving special treatment to the `prelude`.) |
There was a problem hiding this comment.
This feels like a step back into forcing people into this prelude pattern. If I had a small library that would only publish its most important functions, then I'd have to do something like
import utils::colors::prelude::*
import utils::noise::prelude::*
This makes it seem like there is a subgroup of functions not exposed in colors and noise. Even though the whole library could just be those two modules and be imported like this:
import utils::colors::*
import utils::noise::*
You could argue, that it is only a warning, but I would say that a warning is supposed to tell you the "right way to do things". Library authors would be incentivised not to use the 2nd option, even though it looks much cleaner. (in a world where glob imports are fine 😄 )
To be more precise here:
I think that this rule only applies to multiple dual imports makes this an edge case. But I would argue that especially when I import multiple modules from one library this behavior would be confusing and not necessary. A library probably won't break their own semver compatibilty.
|
|
||
| To reduce the odds of these cases, we recommend the following diagnostics. | ||
|
|
||
| 1. Emit a warning [diagnostic](https://www.w3.org/TR/WGSL/#diagnostics) named `builtin_shadowing` if an top level item is declared that is known to conflict with a predeclared identifier. This also helps in the non-library case. |
There was a problem hiding this comment.
Do I understand this right, that this error only occurs when a library calls their published function "unstable_thing()"?
So does it only give out warnings for conflicts between naga features and library glob imports?
| // Requires opt-in | ||
| @diagnostic(off, wildcard_import) | ||
| import lygia::math::*; | ||
| ``` |
There was a problem hiding this comment.
I think examples that would show what is not allowed would be appropriate here, since the above text is about things that are not allowed.
I'm not sure I understand this correctly, so I hope it's fine to ask: And the user of bevy would not get to see this: Unless they are looking at the definition of a bevy function they use in their library? |
|
@Arne-Berner And yes, that is a correct description. The name clashes caused by wildcards can happen at any place in the dependency tree. |
sure, for you it's easy :-). But we can't expect that the user handling errors after an update is an aspiring WESL programmer sitting in their IDE who's read our blog post on how handle glob related errors.. In the js world, npm update, npm user expectations, npm dependency tools, etc. all treat patch/minor failures as upstream bugs. We can't hope that the npm ecosystem will change that expectation for WebGPU. Imagine the user running npm (or cargo) update is not the shader programmer on their team. Or imagine a web dev team, updating html statement loading a shader library from a CDN (no package mgr, no IDE). Or imagine a bot trained to handle npm updates and classify upstream bugs. In all these cases, they're not well positioned (or expecting) to make shader fixes - they just want to get the security update or stay up to date or whatever. AFAICT, we need patch/minor updates to succeed for default users. It's desirable anyway, but also a consequence of choosing to cross publish libraries to the npm ecosystem. |
Isn't that in the context of using warn diagnostics when importing two libraries via wildcard? So someone would have to have used the "don't warn me about that" before it's relevant. And at that point it would mean that it's not an upstream library that has to change one wildcard import and you have to wait for them to fix your problem, but you can fix it yourself.
I have only worked as a JS/TS dev for about halve a year, but if some package that is needed by the shader programmer breaks the whole library I think most people would pin that library and update the security issue in another library. Lastly I would argue that people who get used to something like this: [] + [] // ""
[] + {} // "[object Object]"
{} + [] // 0 (in many consoles this is parsed as a block + unary +)
"5" - 1 // 4
"5" + 1 // "51"Might tolerate a very rare breaking change on npm update for a pure wesl library that has a security patch, whilst the responsible developer is not there. |
What!? But I was always warned against that! |
|
Exactly! Now, to be sure, shorting them out would be dangerous but 12-13V aint enough to overcome the resistance of your skin. Its barely more than 9V and people lick those to see if theybhave power. Anyways: With the requisite knowledge come risks you know you can safely take, and warnings you can bypass. Thats the nature of warnings: if you're knowledgable and careful, ignoring specific ones becomes safe. So, my point is, let the warnings exist for those who don't know they need them, those who know what they're doing won't bat an eye at having to bypass them. (i... might be getting a bit too off-topic and oddly dramatic here. sorry. i feel like i've said my piece now) |
Yeah, future readers, please don't try this with something metal. Or on your electric car. Best really not to take any car advice at all from gpu tooling projects.
I'm tempted to debate further on the happy path philosophy - guiding principles are helpful to figure out. But let me fold here for now, and follow your suggestion for the next draft of this PR. And here's one more argument for making |
just add sentence in the precedence section also drop the packag names can shadow line iit's clear enough from the table
to make clear that other names than 'prelude' are allowable
laundmo
left a comment
There was a problem hiding this comment.
I never said to remove builtin_shadow from the author side...
| **Don't shadow WGSL builtins.** Names like `vec3`, `clamp`, `inverseSqrt` have | ||
| expected semantics that oughtn't be implicitly overridden with wildcards. | ||
| Similarly, avoid experimental Naga/Dawn/Safari builtins. | ||
| - WESL publishing tools should warn when a `@wildcardable` module exports an |
There was a problem hiding this comment.
Hold up, my point was never that it should only show on client side, but that it should also show on client side.
I don't like this being removed from author side either.
There was a problem hiding this comment.
Reasonable to warn in the publishing tool too. Restored.
| | Wildcard import conflicts with wildcard import (when name is referenced) | Error | | ||
| | Wildcard import from a non-`@wildcardable` external module | Error (`wildcard_import`); suppressible | | ||
| | Local declaration or named import shadows a wildcard-imported name | Warning (`wildcard_shadow`); suppressible | | ||
| | Wildcard import shadows a WGSL builtin | Warning (`builtin_shadow`); suppressible | |
There was a problem hiding this comment.
Could be a bit clearer when it shows, namely, on imports.
|
|
||
| - **`builtin_shadow`** fires when a wildcard import shadows a WGSL builtin such | ||
| as `vec3` or `clamp`. Suppress at the import site if the override is | ||
| intentional; the suppression itself documents documents to readers that the |
There was a problem hiding this comment.
| intentional; the suppression itself documents documents to readers that the | |
| intentional; the suppression itself documents to readers that the |
also add note that builtin_shadow client side check is only when referenced
|
A few more syntax ideas from the discord discussion for the module metadata syntax, for import wgsl_play::{play_version, range};
// module metadata syntax options:
module wildcardable, play_version(17); // module never @
module @wildcardable, @play_version(17); // module always @
module wildcardable, @play_version(17); // module mixed @
@play_version(17) module wildcardable; // @ extensions then module
@wildcardable @play_version(17) module; // @ all then module
@!wildcardable; @!play_version(17); // @! instead of module or just @
// item metadata will look like this:
struct Uniforms {
@range(3,5) num_balloons: u32;
}They all work, we're syntax tuning. In addition to the other syntax comments, some more nice to haves:
We can't have all of those things of course. At time of writing, I lean towards options where both This idea (via @stefnotch): has the additional advantage that the |
- add `attribute*` to `import_statement` so `@diagnostic` is grammatical on imports - `module` becomes a reserved keyword
- rationale for @wildcardable syntax - rationale for @wildcardable design
|
Bikeshedding question: should module attributes come before or after import statements? For what it is worth, Rust does usually place them before import statements. |
|
I think the order doesn't matter. |
For built-in attributes like But if The reverse order would either make imported attribute names unavailable there, or break the expectation that imports come before use of imported names. Seems better to let the Aside from attributes, it's nice stay flexible for future to instead use a syntax like: Of course, users can use the fully qualified form too if they prefer: |
Just wanted to point out that WGSL top level items are unordered. So as a WGSL programmer, I don't actually have the expectation that imports must be specified before the names are used. I only expect that usual code will put the imports at the top. Which is why WESL has the current grammar restriction where the imports are before everything else. But don't let this distract you from pushing forward with the proposal. I'm happy with module level attributes appearing after import statements. |
|
I'm happy with this: This |
|
|
||
| A wildcard import imports all top-level declarations from a module. Submodule names and submodule contents are not imported. | ||
|
|
||
| WESL also extends WGSL's `global_directive` rule with a `module_declaration` form, used by `@wildcardable` (see [Wildcard imports](#wildcard-imports)) and reserved for future module-level metadata. `attribute` is the WGSL attribute rule. |
There was a problem hiding this comment.
- add: there can be at most one module declaration per file (?)
or would it make sense to allow multiple?
There was a problem hiding this comment.
I'll change to clarify just one for now. It's not really necessary to have more than one yet afaict, and we can relax to multiple later (or not if we introduce other future semantics on module that mean we don't want two).
There was a problem hiding this comment.
just one, clarified in the PR
| the user may see a conflict in library code they don't expect to modify. | ||
|
|
||
| WESL library publishing tools will address this by expanding wildcards to named | ||
| imports in the published version of the module: |
There was a problem hiding this comment.
Oh, I got confused / forgot about this issue. Yeah it sounds like it has to be a pre-publish process and that's currently not the case. I'd need to think about how to achieve this.
| the import statement to accept the upgrade risk that future versions of the | ||
| imported module may add conflicting names. | ||
|
|
||
| - **`wildcard_shadow`** fires when a local declaration or named import shadows a |
There was a problem hiding this comment.
- add like above: "suppress with ... (how?) on ... (where?)"
where: on the local declaration I suppose?
There was a problem hiding this comment.
good question. I think you're right.
| - **`builtin_shadow`** fires on a wildcard import when a referenced name in the | ||
| module resolves to a wildcard-imported item that shadows a WGSL builtin such | ||
| as `vec3` or `clamp`. Suppress at the import site if the override is | ||
| intentional; the suppression itself documents to readers that the builtins | ||
| have changed semantics. |
There was a problem hiding this comment.
I find it slightly ackward that the consumer has to silence that warning, and not the wildcard module author. WGSL itself has no warning for shadowing built-ins, and I think it would make more sense if we added that warning in WESL, not just for wildcards, and it would be up to the wildcard module author to silence this.
I would not push against consumers also needing the warning, I just find it not necessary.
I would like defer this to a subsequent PR.
There was a problem hiding this comment.
Works for me, can be be a separate PR.
There was a problem hiding this comment.
separated consumer side warning to PR #188 for us to consider at leisure.
and whether that's more readable depends on how many there are.
Fix #130
This adds a simple wildcard imports mechanism with safeguards to reduce the risk of libraries accidentally publishing breaking changes.
See also https://hackmd.io/@stefnotch/SyKtKk8uWx for some prior thoughts on this