-
Notifications
You must be signed in to change notification settings - Fork 23
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
import at local scope #410
Comments
D changed its scoping rules and introduced special cases for local imports because the results of local imports proved to be unnatural. Other downsides:
|
any such tooling worth its salt must also understand constructs such as: when weirdTarget: # in os.nim
discard
elif defined(windows):
import winlean, times
template bar =
import foo2
template baz(a) =
# some logic here
import a
baz(foo3) and handling this works:
but it'd be easy to add a json output (similar to what's done in D via Such tool shouldn't have to understand generics anymore than conditional compilation, it should rely on either compiler or compilerapi.
local imports are one of D's great features and both global and local imports are extensively used in D's stdlib; I've used D extensively for many years and used this feature a lot, and am not aware of anyone actually using D complaining about local imports; the style guide https://dlang.org/dstyle.html even recommends:
it works just fine.
quite the opposite, it disentangles dependencies so you only import what you need. What is entangled is the compiler code, which abounds with top-level imports and resorts to workarounds such as extensive use of include files (with all their drawbacks), forward declarations,
I disagree, when you have a ton of imports at top-level, figuring out where a symbol comes from is really hard and putting everything in scope is not a good design: # sem.nim
import
ast, strutils, options, astalgo, trees,
wordrecg, ropes, msgs, idents, renderer, types, platform, math,
magicsys, nversion, nimsets, semfold, modulepaths, importer,
procfind, lookups, pragmas, passes, semdata, semtypinst, sigmatch,
intsets, transf, vmdef, vm, aliases, cgmeth, lambdalifting,
evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity,
lowerings, plugins/active, lineinfos, strtabs, int128,
isolation_check, typeallowed, modulegraphs, enumtostr, concepts, astmsgs oftentimes such module is only needed inside 1 function or generic, which should have its depedencies self-contained without affecting surrounding code. Using local imports would avoid many of those issues and allow client code to organize their code as they wish instead of being dictated by compiler limitations.
Attaching procs to type is an orthogonal topic that has plenty of issues but this should be discussed in #380 rather than here; this RFC would be beneficial even if attaching procs were implemented. |
That code would not be better with local imports -- on the contrary, it would just provide an illusion of cleanliness where there is none. Not to mention that the compiler's entangled dependencies have the root cause in the philosophy of "every feature must always be supported" so that the VM must be able to access the symbol table and if it were for the likes of you even the backend would simply get to use every feature of the frontend willy-nilly. ("The backend can now also do template expansions via the --expandTemplatesLazy:foobar switch. This is useful...") |
Look at this https://dlang.org/changelog/2.071.0.html#import-lookup and https://issues.dlang.org/show_bug.cgi?id=10378 please. |
I can restrict the feature to the more conservative proc main(group: string) =
from t12733b import group
echo group # uses t12733b.group; nothing surprising since it's explicit in the import
echo t12733b.group # ditto
main("def") The remaining forms of imports can be discussed separately ( |
@Araq PTAL: last commit in nim-lang/Nim#18734 ( |
And it gets worse: Bad code like: proc baz(...) =
from strutils import split
...
proc foo(...) =
from strutils import split
would not go unnoticed. For non-generic procs the only reason why the things it imports would not be in the body (assuming we follow this "best practice") would be references to types from a different module in the proc signature. In other words, this feature and the new "best practice" actually encourages bad code (DRY violations) and being even more sloppy with dependencies. At the same time, we need recursive module dependencies instead of hacks that mitigate the need for it. And we need recursive module deps not because they allow good code practices (they don't), but because they come up all the time when wrapping large C++ code bases. |
I don't have an opinion either way, as long as this doesn't have unforeseen consequences on other language mechanisms. I will however note that local imports are not used much in Python. When they are used, it is for a couple of reasons:
With regards to recursive dependencies, ideally the code reordering mechanism would be improved to the point where it's "production quality" (if that is possible). Right now it technically works, but causes various bugs, and tends to reorder compiler errors as well. |
it's objectively worse, from first principles:
the dependencies can be obtained by grepping for import's just as easily and no less robustly that if they were at top-level, or, more robustly, via --processing:filenames or --dump:out.json to get dependencies, or by looking at generated docs which contain all imports.
the opposite is true. The proposed feature reduces the number of dependencies that end up being processed; not compiling code will always be faster than any other approach; this won't import std/times, pkg/regex unless proc fn[T](a: T) =
from std/times import cpuTime
from pkg/regex import re
... It actually encourages (but doesn't require) being less sloppy about dependencies, by localizing where a dependency is actually needed, so that it's clear who needs what and helps refactoring/re-designing; once you know an API is the only one that uses a heavy dependency foo that you want to get rid of, it's easier to reason about the code.
that's an orthogonal topic (see #416), and the feature is still as usesful in presence of cyclic imports (as evidenced by the case of D which makes extensive use of both cyclic depdencies and local imports, and recommends local imports in their style guide). I'm explicitly not making any style guide recommendation in this PR, merely allowing a pattern that is both useful and leads to better code and slimmer depdendencies. |
No, because it won't pick up imports by unused and exported generic procs that use the feature.
Meh, that's just the old boring argument from assuming that you lack editor integration. In real systems, you cursor over the symbol to see where it comes from and the real alternatives would be to write From these principles we would also always write
Then I might as well claim that we don't need the feature because Golang lacks it... |
nor does Unlike conditional compilation, however, it's actually easy to make docgen report the local imports used in declarations (in particular for generics, we visit those during generic pre-pass), so that the docs will still be able to mention local imports (either in global import section or attaching an import section to a declaration in docs, TBD); so this argument disappears after a small patch to docgen.
I don't know an editor integration that will tell you where an imported module is used. Furthermore, you shouldn't have to use an IDE and compile code just to understand the dependencies in a module; IDE's don't help when reading/browsing code you're not actively developing, which is a very common scenario.
no this doesn't follow form first principles/best practices; this violates DRY; and it definitely doesn't follow from the 1st principles I mentioned above:
you should also mention the fact that go lacks generics which would be the thing that would benefit the most from local imports, due to cutting down on unused dependencies. But more importantly, you're cherry-picking a language that has widely opposite design goals as nim, putting all the emphasis on language design simplicity at the expense of features and user experience. All languages nim takes inspiration from according to its webpage support some form of local imports, either in any scope (python) or in some more restricted sense (ada, modula); more relevant and to the point, these more modern languages support local imports: python This feature removes a long-standing limitation and provides significant benefits to manage dependencies and scoping, in particular, cutting down on optional dependencies in generics, which is hard to argue about; I'm not recommending a style guide change that'd require changing a bunch of code and made explicitly no mention of any such guidelines, but it gives users the choice if they wish to benefit from it. |
Well one third of your original proposal is "examples of languages that support import at local scope", this is terrible for lots of reasons and makes arguing for the feature much harder than it would otherwise be: There many features and many languages and most features are supported by a selected set of languages. So should Nim be the superset of all language features? Hopefully not, so a feature needs to stand on its own merits ("from first principles"). Then you argue that it is: a) syntax sugar so no harm done. AND b) it's a new idiom for generic code enabling "optional dependencies". I tried to point out that the AND here is a contradiction. So this leaves us with a new feature, enabling more stuff with unforeseen consequences. I heavily oppose the notion of "optional dependencies" and I'm quite sure that they impact IC. ("Recompile this module when its dependencies change!" - "what about its optional dependencies?" - "er...")
I didn't: C# does not have the feature, nor do Delphi, Modula 2 and 3, Ada, C++, Fortran. Actually all the languages that Nim took most inspiration from... But as I said, it doesn't matter. Features should stand on its own merits. |
I agree of course that the provenance of the features shouldn't matter, but IMO Ada is an example of a language supporting this. Ada is different than Nim, and an https://www.adaic.org/resources/add_content/docs/95style/html/sec_5/5-7-1.html So I think Ada is in that list of those languages that support the feature, modulo the redundant "with" clause. I like the feature and think Nim would benefit from it. I have yet to see a rational reconstruction of Nim from "first principles" so I can't say local imports follow from or are rejected by these principles. Generally, I like lexical scope, and the possibility of nesting, even if in practice two levels of nesting is almost always enough. |
To the best of my knowledge |
With this restriction, problem detailed at #380 may appear more since I'm not arguing against the feature, any other option for 'local imports hiding local symbols'? |
Months passed, nothing changed my opinion. It looks un-idiomatic and dangerous. In the best case it would "only" produce a massive amount of work and implementation bugs, draining resources from IC and cyclic modules which are much more useful. |
proposal
make
import
work at local scope, which removes a long standing restriction.local imports is a basic feature found in lots of other languages with a module system and improves modularity and symbol scoping and can trim down dependencies to "import what you use" in generic code.
examples of languages that support import at local scope
using
inside a namespace for scoping)benefits
This allows client/library code to avoid top-level scope pollution when a symbol is only needed in a certain local scope (block or proc). Or, if an operator should only be used in some scope, you can use a local import (refs kaushalmodi/ptr_math#2 for pointer arithmetic operators)
For imports inside generics, this furthermore allows the import depedency to only occur when the generic is instantiated, leading to smaller and more localized dependencies ("import what you use").
Finally, this can also help with circular dependencies, e.g. when an import is inside a generic.
PR
this has been implemented, see nim-lang/Nim#18734
The text was updated successfully, but these errors were encountered: