Julia: Remove require, import, and maybe importall and merge into using

Created on 14 Aug 2014  ·  72Comments  ·  Source: JuliaLang/julia

From the mailing list:

Stefan:

I think we should do away with import altogether and just have

using Foo
using Foo: bar

The first would load and create a binding for Foo, make it's exports available as "soft" bindings (what using does now). The second would also load and create a binding for Foo, and make bar available as a "hard" binding (what import does now)."

Seems this is still quite a point of confusion for newcomers.

breaking design modules speculative

Most helpful comment

I like the symmetry of import and export. (As someone pointed out somewhere.)

All 72 comments

We'd need the import Foo functionality too somehow, where you just get the Foo and nothing else

using Foo:?

using Foo: Foo?

To bind Foo to the Foo module (and nothing else):
import Foo
To bind Foo to module Foo, x to Foo.x, y to Foo.y
import Foo: x, y
To bind Foo to module Foo, and all Foo's exported names are bound unqualified:
import Foo: *

This could be using instead, but I feel like this is more in the spirit of import.

This also removes the distinction between bringing something into scope and making it available for method extension. I personally think that's a good thing and makes the module system more understandable, but I wanted to make sure we brought it up.

There is a strong case to be made that a construct that makes all of a module's exported bindings available should make them soft (using) bindings and not hard (importall) bindings. Suppose module A uses module B and defines foo(::Any). If a later release of module B also defines and exports foo(::Any), you don't want them to clobber each other. You also don't want module B to define foo(::Int) and have module A sometimes call that method instead of the method it defined because it is more specific, or to have to list all of the identifiers you want from module B to avoid importing a single conflicting identifier.

But if you have explicitly listed the identifiers you want to import, then there is never a reason to give a soft binding. Either you are not going to define new methods of an identifier, in which case hard and soft bindings have identical behavior, or you are going to define new methods of it, in which case a soft binding is equivalent to no binding and if you want that behavior you should just remove the identifier from the list.

So, in short, I like @StefanKarpinski's proposal. Conceptually we need both hard and soft bindings, but we can get all useful behaviors with a single keyword.

I see your point. In that case I like your proposal that using Foo: (with the colon but no items) seems conceptually consistent. The colon indicates that you're going to limit which symbols to pull in, but you just don't list any.

The empty trailing : looks a little funny, but I think people would get used to it

The trouble with the empty trailing colon is that we currently look for a name on the next line – in other words using Foo: is taken to be incomplete.

Related: #4600

I know that current Julia practice is to import everything into the
namespace of the current module, but I really think that there should still
be a simple and natural way to import just the name of a module.

I also think that the current overloading of using Foo and using Foo.Bar is
problematic; it looks inside Foo but not inside Foo.Bar (unless Foo.Bar is
a module?)

I think in Stefan's proposal, this is addressed with

using Foo: Foo

On Thursday, August 14, 2014, toivoh [email protected] wrote:

I know that current Julia practice is to import everything into the
namespace of the current module, but I really think that there should still
be a simple and natural way to import just the name of a module.

I also think that the current overloading of using Foo and using Foo.Bar is
problematic; it looks inside Foo but not inside Foo.Bar (unless Foo.Bar is
a module?)


Reply to this email directly or view it on GitHub
https://github.com/JuliaLang/julia/issues/8000#issuecomment-52202142.

@kmsquire but what then happens if there is a Foo module inside of the Foo module? That's unfortunately ambiguous.

That's a design error (and causes a warning), so it doesn't matter

I like the version proposed by @ssfrr above the best.

Is there an underlying convention that packages have one and only one module entry point? -- To import Foo means import module Foo of package Foo. Any other module must be a submodule of Foo. Which means there is an implicit correspondence between package Foo, Foo.jl in the package and the module Foo within it.

I ask b/c I am wondering if import could also be used for local/relative importing. Say a project has src/foo.jl and src/bar.jl then in foo.jl:

import bar

would import from src/bar.jl. This is an improvement over using include because it operates within the module system.

Yes, packages, package entry points, and modules are expected to be one-to-one. You can break this convention if you need to, but there's not much need for that since modules are just for naming and aren't functional units. The idea that you're bringing up is #4600 except the syntax for a relative import is import .bar whereas import bar is an absolute import.

@StefanKarpinski Does import .bar actually look for "bar.jl" in the local directory? I was under the impression that import .Bar referred only to a submodule of an already current parent module.

UPDATE: Duh, that's what you propose in #4600. Sorry.

:+1:. If we are going to do this, 0.4 would be a good time to do it.

:+1: Please go ahead and clean some of this up (for 0.4!)

instead of having two import mechanisms to distinguish between extending or not, why not signal extension at the extension site itself via a keyword or annotation at the function definition, e.g. override keyword before function? Simillar to @Override annotation in Java.

This has the advantage that one clearly sees that the function is overriding another (and hence is a method)

That's already possible, @ssagaert. You do it by explicitly writing the module name in the function definition (e.g., Base.print(…) = …), and it seems to be a style that lots of folks are converging upon. The only sticking point is that the syntax doesn't work for all possible names (such as .+, etc.).

(As an aside, please be careful to enclose macros with backticks `` to prevent pestering other GitHub users).

:+1: to @ssagaert suggestion of using @override, the original error message when one tries to extend a method without importing it first reads like this:

ERROR: error in method definition: function Foo.x must be explicitly imported to be extended

So pherhaps @extend could be more suited? I'm not a native english speaker, but my understanding is that _override_ means something like: cancel, void, invalidate, negate, annul, nullify, discontinue, etc.

I think this is more explicit:

@extend Base.show(...) = ...

Than:

import Base: show

# ... several lines here

Base.show(...) = ...

@Ismael-VC Base.show(...) = ... already works without importing anything. import is only necessary if you want show(...) = ... to extend Base.show.

@Ismael-VC override word was just a suggestion. It can be extend or anything else meaningful. Also there should be no @ since in julia that means its a macro (@Overridereferred to Java where it's an annotation).

@simonster thank's I didn't know that!

@ssagaert so you mean a keyword? I tried something like this but I still suck at macro-foo:

module Extend

export @extend


macro extend(x)
    mod = x.args[1].args[1].args[1]
    met = x.args[1].args[1].args[2]
    imp = :(Expr(:import, $mod, $met))
    :(Expr(:toplevel, $imp, $(esc(x))))
end


end

I know this is not general, still I can't make an expression that returns what parse does:

julia> using Extend

julia> type Foo end

julia> @extend Base.show(x::Foo) = Foo
:($(Expr(:toplevel, :($(Expr(:import, Base, :show))), show)))

julia> parse("import Base.show; Base.show(x::Foo) = Foo")
:($(Expr(:toplevel, :($(Expr(:import, :Base, :show))), :(Base.show(x::Foo) = begin  # none, line 1:
            Foo
        end))))

I think that a general and working @extend macro wouldn't change the semantics of the import mecanism.

@Ismael-VC I did but I like the 'trick' of @mbauman also.

I think it might be nice to be able to use . on it's own to mean "this". so you could write expressions such as:
import Base: . (meaning import Base.Base)
or
using ..

i'm pretty sure that would require https://github.com/JuliaLang/julia/pull/11891#issuecomment-116098481 however. maybe it's sufficient to allow spaces before the ., but not after it, to resolve the ambiguity case?

I believe require is already deprecated. Might be nice to add more flexible dot notation on module importing by 1.0, but I doubt we're going to change anything here by 0.6 feature freeze

After a bunch of discussion on this yesterday I'm leaning towards something like the proposals in https://github.com/JuliaLang/julia/issues/8000#issuecomment-52142845 and https://github.com/JuliaLang/julia/issues/8000#issuecomment-52143609:

using A: x, y    # hard imports x and y from A

using A: A       # hard imports just the identifier `A`

using A: ...     # soft imports all of A's exports

using A     # equivalent to `using A: A, ...`

using A.B   # A.B must be a module. equivalent to `using A.B: B, ...`

using A: ..., thing1, thing2    # import all exports plus some non-exported things

Alternative would be to keep import rather than using:

import A             # hard binding for the module `A`
import A: ...        # soft bindings for all names exported by `A`
import A: x, y       # hard bindings for `x` and `y` from `A`
import A: x, y, ...  # equivalent to doing both of the previous two

The general rule for whether a binding is hard or soft would be simple: any explicitly requested name is a hard binding, any implicitly given binding is soft.

Edit: there's some desire to add to this scheme some shorthand for import A; import A: ... which is approximately what using A currently does (the only difference being that using A currently soft imports A); this could either continue to be using A or import A... has been proposed.

Yes, I think that's also a good proposal. Basically comes down to whether one prefers using or import.

Addendum, we could keep using A as a shorthand for import A; import A: ... – coupled with getting rid of the current behavior that each module has an exported binding for itself, which is why using A causes there to be a soft binding to A available.

I would be pretty disappointed if we still had multiple keywords after all this.

I like the symmetry of import and export. (As someone pointed out somewhere.)

Always doing "hard bindings" doesn't seem like the safest default in terms of method extension. There was the related but somewhat separate proposal of requiring module qualification for method extension that could remove the need for "hard bindings."

Hard bindings are also needed for this purpose:

import Package: x
x = 1   # gives an error

And crucially, this proposal would not always do hard bindings --- only for things you explicitly list. Requiring that one say import instead of using doesn't add much safety.

The safety comes from the majority of places that aren't doing method extension that can get away fine with a soft binding. I think we should still have a way of asking for a specific soft binding without having to import all of a package's exports (or getting a specific soft binding that isn't exported).

Do we still have the warning for overwriting an imported binding?

I think requiring module qualification is a good idea. Right now to know if a function is being introduced or a method extended you have to look at the entire package's contents for an import A: func statement.

Do we still have the warning for overwriting an imported binding?

I believe it's actually an error now.

There's another proposal floating around that keeps both keywords, but still simplifies things a bit:

  1. Add the syntax import A: ...
  2. Deprecate using A:
  3. In using A.B, require A.B to be a module, and document that it's a shorthand for import A.B; import A.B: ....

This way, everything can be done with just import, but using X is available for convenience. This would also be especially easy to transition to.

BTW, using looks inconsistent as I posted here. If we keep using, it should be renamed to use if possible.
I think we should require module quantification when extending functions since its meaning is much clearer than the import-then-extend pattern.

My favored approach:

  • Require an explicit module prefix for extension
  • If you only want the module name and not its symbols, use using A: A
  • Remove import and the kind of binding it creates

Things that would need to happen to implement https://github.com/JuliaLang/julia/issues/8000#issuecomment-327512355:

  • change the behavior of using A to hard import A
  • remove support for using A: x
  • remove support for using A.x where x is not a submodule
  • remove support for import A.x where x is not a submodule
  • add support for the ... syntax to import

using A: x is used often and is very useful. You're saying you want x in your namespace but you don't want to extend it. In import A: x, you're saying you want to be able to extend x. There is a meaningful distinction between having a function available for use and being able to extend it.

Come to think of it, I'd say the single biggest problem here is that using A.B does two things: if B is a module, it soft imports all its exports, and otherwise just soft imports B. I think we should just fix that, and make using A.B only allowed for modules, and have using A: a, b for soft importing specific bindings one at a time.

I would prefer if there was one way to write import A: x instead it being equivalent to import A.x.

I vote for import A: x since we can also do; import A: x, y, @z but import A.x, A.y, a.@z would look ugly.

Does this being removed from 1.0 mean that we will be left with both using and import for 1.0? That is a bit unfortunate in my opinion.

How about:

  • Force module prefix for extension. This will make code clearer that an extension is intended instead of it being accidental from an import in some other file.
  • using A becomes import A: ...
  • using A.X (X is a module) becomes import A.X: ...
  • using A: X (Xis not a module) becomes import A: X
  • import A: X is not changed but you cannot automatically extend X (see the first point)
  • delete the using keyword

Am I missing some use-case? Maybe this was already suggested...

What I like about being explicit about the module when extending is that extension becomes much more local. Right now, when a method is extended, it is common to put the import very close to the top of the module (which might be in a completely different file!). When the extended method is deleted the import statement is usually forgotten.

I think that's essentially the same as my proposal above, which got a fair amount of support. @JeffBezanson really wants to keep using A at least for ease of use and using A: x because apparently (I'm not sold on this), it's an important use case to be able to import a binding in such a way that you cannot extend it. There are some proposals to go the other direction and replace import with using but none of them really got much traction (import seems more fundamental).

I think the difference is in:

  • import A: x, y # hard bindings for x and y from A

Assuming the meaning of "hard binding" is such that you can extend it without module prefix, in my version there are no hard bindings. If you want to extend, you module prefix it exactly where you extend it. No spooky import statements in other files changing the meaning whether something is an extension or not.

and using A: x because apparently (I'm not sold on this), it's an important use case to be able to import a binding in such a way that you cannot extend it.

Isn't forcing module prefix dealing with that? Or are we talking about non modules like:

module M
    x = 1
end

Both import M: x; x = 2 and using M: x; x = 2 gives the same warning message so I don't see what the problem is there...

Keeping using A for ease of over import A: ... seems a bit excessive in my opinion.

Isn't forcing module prefix dealing with that?

Yes; if you had to qualify functions to extend them then this point would be irrelevant.

Keeping using A for ease of over import A: ... seems a bit excessive in my opinion.

I see it the opposite way; making people switch from using A (which is nice and short and we're all quite used to) to import A: ... just to satisfy an artificial requirement that there be only 1 keyword is excessive.

From reading the thread, it seems the main value in having two keywords is to differentiate between bindings that can be extended or not (hard binding). With that in mind, I think there are two viable solutions:

  • 1 keyword + require prefix for extending
  • two keywords, one for normal (non-extending) use, and a second for extending use I suggest using and extending in this case. import is fine, but extending makes the reason for the existence of a 2nd keyword obvious

In either case I suggest using should be as it is now with the addition of one of the following to bind only the module Foo:

  • using Foo: nothing (works now)
  • using Foo: Foo (works now)
  • using Foo: (can be added later)

Then extending should behave identical to using with the only difference being that you can extend bindings brought in with extending, and possibly disallow extending Foo so that it has to be explicit.

Current behaviour

| | make available (using) | make extendable (import)|
| ------------------- | -------------------------- | ---------------------- |
| only module | using module: module or using module: nothing | import module |
| everything exported | using module (side effect: acts like import module as well) | ? |
| particular things | using module: x,y | import module: x,y |

Suggestion

| | make available (using) | make extendable (import) |
| ----------------- | ------------------------ | -------------------------- |
| only module | using module | import module |
| everything exported | using module: * | import module: * |
| particular things | using module: x,y | import module: x,y |

The nice things about this is, that importing more corresponds to writing more. I.e. you start with using module and if you want to import a variable directly into namespace you add a : x instead of removing a nothing or module. It also means that the shortest thing you type includes the least.

You can also do using: *,x to make everything exported available and x which was not exported.

Compromise for backwards compatibility:

| | make available (using) | make extendable (import) |
| ----------------- | ------------------------ | -------------------------- |
| only module | using module: | import module: |
| everything exported | using module: * | import module: * |
| particular things | using module: x,y | import module: x,y |

keep around using module and import module with current behaviour for backwards compatibility but deprecate it.

@FelixBenning: import Module currently does not (by itself) make anything extendable any more than using Module, it just loads the code and brings Module (and nothing else) into the namespace.

Just to mirror what I've said on slack and so that it doesn't vanish in the slack hole:

I don't think making using X: * the default for making every exported thing available vs. just using X will make people necessarily more wary of what they import. I know, pointing to how others do it is considered bad form, but Python basically has those semantics with their import X and import X: *, yet their ecosystem is littered with those star imports 🤷‍♂️ (and they're famously hating that) I don't think the marginally longer text one has to type stops people from doing what they consider to be the most convenient: just import/use everything and let the compiler figure it out. That's why I'm wary of the magic bullet of making people write that star explicitly.

Further, import module: * and using module: * is unavailable for the proposed meaning. It already has a meaning as * is a valid Julia identifier and can be imported/used just like + or the word mul.

@tpapp either I have misunderstood the documentation again, or import Module makes Module.x extendable. While using Module: x does not make Module.x extendable. Therefore import Module makes something available for extension and using Module does so too, which is why I noted that using has that side effect.
grafik
(from https://docs.julialang.org/en/v1/manual/modules/)

It does not really matter which one of us is right - in either case the current situation is oviously a mess if we can't even figure out what everything does.

@mbauman good point - I forgot about that. I don't really care about the * just the structure of having using mirror the things import does with the difference of import vs using being whether or not things become extendable. So if there is a more appropriate symbol - all, __all__, everything, exported, ...? I am all for it. I just think that importing more should probably be reflected by typing more.

But if you do not want that at all, you could of course also go for

| | make available (using) | make extendable (import) |
| ----------------- | ------------------------ | -------------------------- |
| only module | using module: module | import module: module |
| everything exported | using module | import module |
| particular things | using module: x,y | import module: x,y |

or

| | make available (using) | make extendable (import) |
| ----------------- | ------------------------ | -------------------------- |
| only module | using module | import module |
| everything exported | using module: module | import module: module |
| particular things | using module: x,y | import module: x,y |

But whatever the end result is, it should be consistent. And at the moment it just is not.

using A makes A.f extendable, _not_ f by itself. In order to extend _just_ f without declaring from which module you want it extended, you have to import A: f explicitly. Otherwise you'll still have to qualify it.

Check the following for the semantics of using

julia> module A
        export f
        f() = "no args in A"
       end
Main.A

julia> f()
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[2]:1

julia> using .A

julia> f()
"no args in A"

julia> f(1)
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
  f() at REPL[1]:3
Stacktrace:
 [1] top-level scope at REPL[5]:1

julia> f(x) = "one arg where?"
ERROR: error in method definition: function A.f must be explicitly imported to be extended
Stacktrace:
 [1] top-level scope at none:0
 [2] top-level scope at REPL[6]:1

julia> A.f(x) = "one arg where?"

julia> f(1)
"one arg where?"

And this for the semantics of import:

julia> module A
        export f
        f() = "no args in A"
       end
Main.A

julia> f()
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[2]:1

julia> import .A

julia> f()
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[4]:1

julia> A.f()
"no args in A"

julia> f(1)
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[6]:1

julia> A.f(1)
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
  f() at REPL[1]:3
Stacktrace:
 [1] top-level scope at REPL[7]:1

julia> f(x) = "one arg where?"
f (generic function with 1 method)

julia> f(1)
"one arg where?"

julia> A.f(1)
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
  f() at REPL[1]:3
Stacktrace:
 [1] top-level scope at REPL[10]:1

julia> A.f(x) = "one arg where in A"

julia> A.f(1)
"one arg where in A"

@FelixBenning: yes, I think you misunderstood. FWIW, I think that the "... makes Foo.x extendable" is a confusing way to approach the distinction --- you can always define methods to fully qualified function names. What happens with using Foo: x is that Foo itself will not be brought into the namespace.

Incidentally, rereading this topic I am wondering if #25306 brought us to a sort of local optimum, and the only decision that remains is whether we need non-extensible imports into the namespace (currently using Foo: f). But since that is breaking, the manual chapter would perhaps benefit from a rewrite in the meantime, many users find the whole thing confusing.

This is how I would approach the status quo in the docs:

  1. using M loads the module, and brings M and its exported symbols into the namespace. This is the only case where exports matter.
  2. once you have M, you can use qualified names like M.y to (a) access non-exported symbols and (b) add methods to functions, regardless whether they are exported or not
  3. Julia prevents you from adding methods to functions unless you use qualified names like M.f, or ...
  4. ... import M: f, then you can just use f when defining methods.
  5. import M and using M: x are for bringing just M or x (and not M), respectively, into the namespace, respectively. Some people like to use these forms in package code to make sure their namespaces are kept clean (link relevant style guides here).

The order more or less reflects use cases encountered with more advanced usage. All of the above applies to submodules, with M.A in place of M.

What about this:

  • using M brings M into scope
  • using M: x brings M.x into scope (as x)
  • using M: ... brings all exported symbols of M into scope
  • using M: x, ... brings all exported symbols of M into scope and also x (which might be unexported)
  • There is no import. You need to use the qualified name for extending a function. (Or using M; const foo = M.foo like one could already do now.)
  • In all of the above, M could also be a submodule, e.g. Foo.Bar and x could also be x as y with the current meaning.

Or we use import instead of using which then makes this equal to https://github.com/JuliaLang/julia/issues/8000#issuecomment-355960915.

A very common use (especially in "scripts", but also packages for certain styles is)

using Foo, Bar, Baz

and just relying on exported symbols. There would be some benefit from keeping this the simplest, possibly as simple as it is now.

@tpapp

I think you misunderstood. FWIW, I think that the "... makes Foo.x extendable" is a confusing way to approach the distinction --- you can always define methods to fully qualified function names.

Okay, so can I extend Foo.x after using Foo: x? Because if yes, then the documentation is not complete (see my screenshot). If no, then I have perfectly understood how these statements work and quite obviously import Foo did something to make Foo.x extensible. Therefore it literally makes Foo.x available for extension. In every sense of these words. Sure it does not make x available for extension, but that is what import Foo: x is for

Okay, so can I extend Foo.x after using Foo: x?

Not unless you bring Foo into the namespace somehow (eg using Foo).

I have perfectly understood how these statements work

I am not quite sure about this — however, if you have questions about modules and namespaces, please kindly use the Discourse forum.

quite obviously import Foo did something to make Foo.x extensible

Only in the sense that you have to be able to refer to a function somehow using a qualified name before adding methods to it.

then the documentation is not complete

I think it is, in a kind of quirky way. In that particular example, if all you do is using MyModule: x, p; then no methods are available for extension, so the table is correct.

I do agree that it could be written better, as I said above. A lot of people not used to namespaces find it confusing. And, TBH, the whole using/import circus is mildly confusing, hence this issue.

@tpapp

Okay so here is the thing: It is absolutely not obvious that you can extend every function with the full name if the module is in the namespace. I know that this is current behaviour, but I don't think that anyone who does not know that already would assume that. Especially because being in the namespace does not always mean that it is also extendible. If I do using module:x I can not extend x. While I can extend x, if I use import module:x. Therefore it is a reasonable assumption, that the difference between using and import is whether or not you can extend the the imported functions.

Using this assumption it would make sense, if using module would only allow the use of module.x but would not allow the extension of module.x. While import module would allow the extension of module.x. So if you take this assumption and read the documentation, you will find that two things about that are wrong.

  1. using module does allow you to extend module.x. Therefore from a learners prespective using module has the side effect, that it also acts like import module - i.e. it makes module.x extendible. And making things extendible is an import thing, not a using thing
  2. in contrast to import, using does not only make module available, but rather everything in it.

So this is what I tried to represent with my table. If two different words are used (import/using) then they should do two different things and that should be clear cut. If using sometimes allows for extension and other times it does not that is not predictable behaviour.

A good alternative is to remove import completely as @martinholters suggests. You just simply can not have two words which are just randomly used for certain things. If import means making things extendible when importing certain functions, while using module: foo does not allow that, then the same behaviour should happen when you only include module in the namespace.

Just because you can extend everything by its full name right now if the module is in the namespace, does not mean that this is an obvious or straightforward thing.

Either being in the namespace is enough, then I should also be able to extend x if I included it in the namespace with using module:x or being in the namespace is not enough, and then I should also not be able to extend module.x when using the using command.
Or third option: there is no import and you simply have to extend functions with their full name all the time.

It is absolutely not obvious that you can extend every function with the full name if the module is in the namespace.

I agree, which is why I argue for a rewriting of the docs. However, that is tangential to the current issue. It would be best to keep (1) proposals for improving the documentation on the current API and (2) proposals to redesign it separate, even if the two are somewhat related. I plan to make a PR for (1) soon.

@tpapp You can not document something which does inherently not make sense. Neither of these documentations is correct:

  1. "You can extend anything which is in the namespace" (because using module:x does not allow you to extend x)
  2. "Being in the namespace is not sufficient for extension, this is why separate words using and import exist" (false because for full names it is in fact sufficient to be in the namespace - stopping that to be sufficient was my suggestion)
  3. "You can extend anything in the Namespace by its full name" (which would require import module:x to stop existing to be the full truth - @martinholters proposal)

Any of these would be acceptable. But none of these are actually reality. Reality is currently that there are two different words used for "importing stuff" but they don't actually have a distinct use case. It is like a for loop sometimes behaves like a while loop if you iterate over booleans. No amount of documentation will make that non-confusing

You can not document something which does inherently not make sense.

The current modules API is well-defined, so it can be documented (it already is, I just think it should be better).

Neither of these documentations is correct:

Please be so kind as to wait for my (or someone else's) PR and comment on the actual text that will be there. Positing hypothetical documentation and then claiming that it is incorrect is just adding noise to this discussion.

@tpapp maybe I should have said,

you can not document something in a non confusing way which does not inherently make sense

I mean in a sense the code is all the documentation you need right? Whats wrong with that? The time it takes to digest it. And I currently see no brief way to describe how it works, because it is riddled with exceptions. Exceptions which should not be there in the first place.

Do you really not see what I am trying to convey?

I think there is general agreement that the current situation is unnecessarily complex and inadequately documented. And no doubt, simplifying the machinery will make documenting it easier. But such simplification will be breaking, so cannot be done before 2.0. So is your point that improvement to the documentation before that wouldn't make sense? Then I disagree. I do see two separate issues: Simplification to be done with 2.0 (which this issue is about) which will (hopefully) include the necessary documentation updates and improving the documentation of the current workings which by reading this thread seems badly required but is another issue.

After doing #38271, I think that outstanding questions are

How to handle adding methods to functions in other modules

  1. always require fully qualified names (Foo.bar() = ...),
  2. also just allow when the symbol is in scope (using Foo: bar; bar() = ...)
  3. (2), but bring into scope in a special way (status quo, import Foo: bar; bar() = ...)

Which syntax handles export lists and just the module name

  1. using Foo brings all exported symbols in Foo to scope, import Foo just the module (status quo)
  2. using Foo: ... or some other similar syntax, then using Foo just the module.

(1|2) & 2 would allow unification into a single keyword, either using or import, at the cost of losing single-line

using LinearAlgebra, Random, StaticArrays

I am not sure it is worth it, even without considering the breaking change.

This is not one of those problems that offer a clean solution that the original design just missed; there are trade-offs. I would wait and see if better documentation can improve user experience while keeping the current (1.0) setup.

For 2.0 I think just a clean up of the syntax to be more consistent and descriptive of what is actually happening would be nice. Something like:

| Before | After |
|-|-|
| using Foo | useall from Foo |
| import Foo | use Foo |
| using Foo: a | use a from Foo |
| import Foo: a and import Foo.a | extend a from Foo |

Was this page helpful?
0 / 5 - 0 ratings