Julia: module and import aliasing

Created on 5 Sep 2012  ·  96Comments  ·  Source: JuliaLang/julia

For the module system, we can import and use a module with dot notation:

import ArgParse
... 
    ArgParse.add_argument(p, "--opt1") 
...

This can be useful to prevent namespace pollution. Because of the verbosity of module names, however, it would be nice to have module aliases:

import ArgParse as ap 
... 
    ap.add_argument(p, "--opt1") 
...
design modules speculative

Most helpful comment

this issue has been there a long time, even though some extensions should still be discussed, the

import LongPackage as longpkg

itself seems pretty reasonable.

All 96 comments

I realized minutes ago that you can do the following:

import ArgParse
ap = ArgParse

ap.add_argument(...)

I still think it would be nice to have the "import as" notation as syntactic sugar though.

While I'm here and thinking about it, it would also be nice to have a way of importing multiple specific functions in the same import line, as mentioned in @JeffreySarnoff 's forum post. A search through the issues didn't reveal such a request yet.

Something like

# from the forum post
import Module.(export1,export2,export3)
# or
import Module.[export1,export2,export3]
# or maybe
import Module.{export1,export2,export3}

It's a different issue than this one, but related. Should I

  1. update this issue, or
  2. create a new issue

(I'm assuming the usefulness of such a feature is not controversial...)

Edit: This works now with import Module: export1, export2, export3

I think that it should also support e.g.

import ArgParse.add_argument as addarg

to be able to rename module members upon import.

Wondering how renamed function names would participate in dispatch?

It would have to work basically like an alias, equivalent to const foo = Base.bar.

It would have to work basically like an alias, equivalent to const foo =
Base.bar.

When we import a function, we either plan to use it or override it. Given
the above, with

import Base.bar as foo

we can definitely use foo as Base.bar, but would defining foo also override
Base.bar? Should it?

This is an old issue, but I think it is time to revisit this as we are approaching 0.2

In writing codes, I found I always wanted to write import NumericExtensions as ne and ended up reminding my self this hasn't been supported.

I guess this is not too difficult to implement, and in my opinion, this is much nicer than writing

import NumericExtensions
const ne = NumericExtensions

+1

+1

Is this still relevant? I personally dont' mind just doing the extra foo = Foo for module aliasing, but it sounds like there was some consensus to support aliasing sugar. Someone who feels strongly enough may have to do a PR themselves.
Note however that x as y was one of the strongest contenders for convert(y,x) syntax mentioned in #1470. Seems like it wouldn't be too difficult to disambiguate the two, but just noting.

The value of this feature is that you could avoid importing the original name.

+1
Would be great to have this feature.

+1

Rather than introducing a new keyword as, how about using the => operator, as in

import Tlahuixcalpantecuhtli => Venus: xxxxxxxxx => x9, yyyyyyyy => y8, zzz

This would orthogonally allow aliasing of the module and any subset of variables in the same syntax.

I don't know, that seems really ugly to me.

import Tlahuixcalpantecuhtli as Venus: xxxxxxxxx as x9, yyyyyyyy as y8, zzz

Is that any better? I find it far less readable.

I personally find as to be more self-explanatory than =>, which seems like it could mean any number of things.

full disclosure: python bias.

I like the => syntax better than as.

I like bikesheds that get stuck in Nash equilibria.

Why would you rebind the inner symbols of a module? Its ugly because you really should not be doing that.

I like Haskell's import syntax. I feel Python's namespaces are more granular than Julia's modules. Julia's module's feel more in spirit to haskell's modules, so maybe we should look there for inspiration.

I agree with @ssfrr , the as keyword is much more self-explatory, and is alo familiar to Python/Haskell users. To solve @StefanKarpinski problem with readability, python-like approach would also help:

from Tlahuixcalpantecuhtli as Venus import xxxxxxxxx as x9, yyyyyyyy as y8, zzz

I feel like I'm talking to the REPL. But I know that this kind of suggestion is not feasible.

from Tlahuixcalpantecuhtli as Venus import xxxxxxxxx as x9, yyyyyyyy as y8, zzz

This is one of my least favorite syntactic choices in Python. To change the meaning slightly you have to rearrange the entire line radically, changing the initial keyword and the ordering of everything. It looks completely different from other imports, obscuring that fact they that do almost the same thing. This is a terrible syntactic design, imo, giving far too much weight to being superficially English-like.

:+1: for as, :-1: for from

import a => b: c => d, e => f feels a little too perl-esque to me.

I never heard of Nash equilibria and after reading about it I don't think this constitutes one. But the funny thing is, while this bikeshed has 10 responses in one hour, a proposal on #6984 which strikes me as very important (but more difficult) has gone 4 hours with no comment!

As for as vs. =>: either will be a nice improvement over the present situation.

Good point @BobPortmann on #6984.

I think => is great for this, FWIW.

But the funny thing is, while this bikeshed has 10 responses in one hour, a proposal on #6984 which strikes me as very important (but more difficult) has gone 4 hours with no comment!

In Parkinson's original analogy, this issue is the bike shed and #6984 is the nuclear reactor.

:)

I hate to stir this pot but I think I prefer as. => is a bit vague, and it's odd for this to share syntax with dictionaries. Maybe just = makes sense: import Foo: x = y, in analogy to the const x = Foo.y you would do now.

If nothing else, that table in the Haskell documentation is really helpful. I've often (especially when first learning julia) gotten confused as to when to use import/using/require, or when to do import Mod.thing vs import Mod: thing. I was actually just looking for the explanation of the colon syntax on imports and couldn't find it.

@jakebolewski it seems like the Haskell syntax is pretty close, with Julia's using being equivalent to Haskell's (nonqualified) import, and Julia's import being equivalent to Haskell's import qualified. Is that right?


As a starting point can someone who knows better confirm that this is a good summary of the currently-available import/using syntax? Say we have a module Mod that exports functions x and y, and also has non-exported functions p and q.

import Mod brings in Mod.x, Mod.y, Mod.p, and Mod.q. The imported functions are all available for method extension

using Mod brings in x, y, Mod.x, Mod.y, Mod.p, and Mod.q. x and y aren't available for method extension, but Mod.* are.

import Mod.x, Mod.p brings in x, p, Mod.x, Mod.y, Mod.p, and Mod.q. They are all available for method extension.

import Mod: x, p is the same as import Mod.x, Mod.p

using Mod.x, Mod.p brings in x, p, Mod.x, Mod.y, Mod.p, and Mod.q. x and p are not available for method extension but Mod.* are.

using Mod: x, p is the same as using Mod.x, Mod.p

As far as I can tell using Mod is the only usage that cares what is exported by Mod.

It would be really useful to have this summary on Module's docs! Can someone confirm this, so I can prepare a PR?

It seems like the least contentious version is:

import Tlahuixcalpantecuhtli as Venus: xxxxxxxxx as x9, yyyyyyyy as y8, zzz

I'm cool with that. The assignment version is interesting though, testing it out:

import Venus = Tlahuixcalpantecuhtli: x9 = xxxxxxxxx, y8 = yyyyyyyy, zzz

I thought I was going to hate that, but it's actually pretty nice. I like that the names that are introduced in this module come first and are most prominent – in many ways that's the most important thing.

@jakebolewski: Why would you rebind the inner symbols of a module? Its ugly because you really should not be doing that.

You may want to import bindings with clashing names from two different modules and this allows that. You could also fully qualify them, but if we're supporting aliasing, we might as well support this too.

Some well placed newlines make either more readable, IMO:

import Tlahuixcalpantecuhtli as Venus:
    xxxxxxxxx as x9, 
    yyyyyyyy as y8,
    zzz

or

import Venus = Tlahuixcalpantecuhtli: 
    x9 = xxxxxxxxx, 
    y8 = yyyyyyyy, 
    zzz

I'm finding that I prefer the assignment version quite strongly. Import is a form of assignment, after all.

It would be really useful to have this summary on Module's docs! Can someone confirm this, so I can prepare a PR?

@brk00, go for it!

@kmsquire, I'll work on it today!

But before doing that, I want to make sure that I understand it well. In @ssfrr example, suppose I'm on the REPL and type using Mod. The imported functions x and y aren't available for method extension. It means that, if I declare another x and y functions, I will no longer have access to the Mod's methods I once imported, only to the new ones?

This is what happens if you try to extend a function without importing it:

julia> module Mod

       export x, y

       x() = "x"
       y() = "y"
       p() = "p"
       q() = "q"

       end

julia> using Mod

julia> x()
"x"

julia> p()
ERROR: p not defined

julia> x(n) = n
ERROR: error in method definition: function Mod.x must be explicitly imported to be extended

You can add a method like so:

julia> import Mod: x # or import Mod.x

julia> x(n) = n
x (generic function with 2 methods)

julia> methods(x)
# 2 methods for generic function "x":
x() at none:5
x(n) at none:1

Well, I was confused by the name resolving behavior:

julia> module Mod

       export x, y

       x() = "x"
       y() = "y"
       p() = "p"
       q() = "q"

       end

julia> using Mod

julia> x(n) = n
x (generic function with 1 method)

If I assign a new function to x before _using_ the one I just imported with using Mod, the error isn't raised. But if I, for example, call x() before the new assigment (like you did in your example), the error is raised indeed.

Ah, interesting! I didn't know about that. I just happened to call x to demonstrate the namespacing after the using.

Maybe one of the core devs can shed some light. What's happening when x is called that triggers the error on attempted overloading?

I find the aliasing suggested by @carlobaldassi above very useful in practice.

I am wondering whether import could just return the module object, so that it could be aliased directly with an assignment, with or without const? Eg

const Shortname = import ModuleWithLongName

instead of

import ModuleWithLongName
const Shortname = ModuleWithLongName

This would require no new syntax, just a combination of existing syntax elements. I realize that currently import is a "statement", called purely for side effects, and has no value. If the parser does not allow this, I would prefer import("Module") or something similar.

I think the option to alias modules might be nice, but I also think that it there's a downside that no one has mentioned: it could significantly obscure code. From the perspective of a code reader, it's nice to only have only one descriptive name for a module.

Pretty trivial to look up though? The shortened forms will also likely standardise for popular packages like np for numpy.

I'd still prefer numpy to standarized np and even Arrays to numpy. numpy sounds like a cross between a 1950's dance move and a deadly plague.

can easily just write:

Base.require("ArgParse")
const ap = Main.ArgParse

this doesn't need a syntax for 1.0

I feel that we should have a syntax for this. Yes, we can live without it since you can do require and a const assignment, but using require is pretty ugly and the lack of a convenient syntax discourages people from renaming things as they want/need to.

The const trick being easy falls short when one needs to alias a macro, this is the way I do it, and it took me a while to figure out:

julia> macro foo(a, b, c)                                  
           a, b, c                                         
       end                                                 
@foo (macro with 1 method)                                 

julia> macro f(args...)                                    
           Expr(:macrocall, Symbol("@foo"), args...) |> esc
       end                                                 
@f (macro with 1 method)                                   

julia> @f 1 2 3                                            
(1,2,3)                                                    

In ImportMacros.jl I'm implementing:

  • @from Foo.Bar use foo as f
  • @from Foo.Bar use @foo as @f

See: https://github.com/fredrikekre/ImportMacros.jl/pull/3

@Ismael-VC

Try

@eval const $(Symbol("@foo")) = $(Symbol("@bar"))

which is uglier, but shorter and perhaps more clear than defining a macro locally

@TotalVerb thanks again for your help!

It seems the only piece of functionality missing from Julia + ImportMacros is to alias both the module and a mix of aliasing a variable number of objects from such module and importing others without alias:

  • @from Foo.Bar as B use foo as f, @bar as @b, baz

Or without the from:

  • import Foo.Bar as B: foo as f, @bar as @b, baz
  • import B = Foo.Bar: f = foo, @b = @bar, baz

The last one seems not possible with macros:

julia> parse("@from Foo.Bar as B use foo as f, @bar as @b, baz")
:(@from Foo.Bar as B use foo as (f,@bar(as,(@b(),baz))))

julia> parse("@import Foo.Bar as B: foo as f, @bar as @b, baz")
:(@import Foo.Bar as B:foo as (f,@bar(as,(@b(),baz))))

julia> parse("@import B = Foo.Bar: f = foo, @b = @bar, baz")
ERROR: ParseError("unexpected \"=\"")
 in #parse#310(::Bool, ::Bool, ::Function, ::String, ::Int64) at .\parse.jl:184
 in (::Base.#kw##parse)(::Array{Any,1}, ::Base.#parse, ::String, ::Int64) at .\<missing>:0
 in #parse#311(::Bool, ::Function, ::String) at .\parse.jl:194
 in parse(::String) at .\parse.jl:194

This doesn't actually need to be decided for 1.0 since all the proposed syntaxes are either errors or total nonsense.

What should happen to a module alias when the original module is redefined, e.g., by a reload("...") on the REPL? With the const M = MyModule approach, M still refers to the old module after reloading, so any newly defined names in MyModule will only be accessible by MyModule.name but not M.name.

In that case, an alias syntax should be more than syntactic sugar for a const M = Module, but instead make sure that M always refers to the same thing as MyModule.

This would also simplify the REPL workflow since I can continue to import a module during development but can refer to it via a shorter alias without having to reestablish a (non-const) alias every time I reload.

As of right now, reload is removed so this concern is no longer applicable.

This also applies for include, or am I missing something? In general, when a module is redefined, the imported module name refers to the new definition while an alias defined by assignment refers to the old definition, right?

No that should not be a concern. The imported name will NEVER be changed when a module is redefined, including normal import. What's changed is the module that will be imported if you import it again. The imported has always been just a binding.

Ok, maybe I did not express myself clearly, so here is an example: If you create a file test.jl with the content

module Tst
f() = 1
end

you can load it, import Tst, define an alias to Tst, and everything is fine:

julia> include("test.jl")
Tst

julia> import Tst

julia> const Tst2 = Tst
Tst

julia> Tst.f()
1

julia> Tst2.f()
1

If you now change test.jl to

module Tst
f() = 2
end

and re-include it in the REPL, the module and its alias behave differently:

julia> include("test.jl")
WARNING: replacing module Tst
Tst

julia> Tst.f()
2

julia> Tst2.f()
1

I see why this is the case, but I think this should not happen when you create an alias with something like import Tst as Tst2.

I tested this on 0.6.2, and at the moment I can't test it on 0.7, so I don't now if this is still the case.

You are not importing it at all. The module you include is already defined in your scope. The import is a no-op.

True, but this is not the point. The point is what happens when I do something like the hypothetical import Tst as Tst2. Then this statement is not a no-op but creates an alias.

If this alias works like a const Tst2 = Tst, then redefining the module Tst will make the alias point to the old version of the module. This is not desirable in any case. Either the alias should point to the new version of Tst as well, or the user should be forced to re-establish the alias. But in the latter case, using the old alias without re-establishing it should throw an error instead of pointing to the old module.

That is entirely the point. This issue is about adding a function to import not to module definition. Your import is no-op and after removing it your code does not have any import or using anymore so it's unrelated to this issue. Please change it so that the module is not defined in the same scope as import.

import Tst as Tst2 will work the same as import Tst in terms of what is binding to that name, which is always a binding and not anything magic that you want. This is NOT about adding an alias when defining the module.

Put it another way, you code is basically,

a = Ref(1) # include
a = a # import
b = a # alias
a = Ref(2) # include again

And it doesn't make any sense to complain about the behavior of the b = a since the a isn't bind by the a = a at all. All what you see is the side effect of defining the module in the same scope as the import (i.e. a = a and a = Ref(...) in the same scope) in a bad example.

Ok, I just wanted to point out that in some situations an alias defined by an assignment would not work as expected. Maybe I chose a bad example. I thought that might be important for a syntax that creates a module alias (whether or not related to import), but right now this is hypothetical anyway. Sorry for the noise.

this issue has been there a long time, even though some extensions should still be discussed, the

import LongPackage as longpkg

itself seems pretty reasonable.

Bump. I am wondering what the best solution - for now - looks like? Here's what I am doing:

import LinearAlgebra
const linalg = LinearAlgebra

Please correct me if there's a better solution!

That is the current standard.

What about a ~ syntax? I don't know the ramifications, but it has a nice aesthetic. Also, ~ is used as a symbol for approximate.

import LongPackage ~ lp

@JeffreySarnoff quoting Stefan

@StefanKarpinski

This is one of my least favorite syntactic choices in Python. To change the meaning slightly you have to rearrange the entire line radically, changing the initial keyword and the ordering of everything. It looks completely different from other imports, obscuring that fact they that do almost the same thing. This is a terrible syntactic design, imo, giving far too much weight to being superficially English-like.

In this case, ~ would have nothing to do with English-like aspect of Python syntax. I think when I see ~, I just recognize the mathematical concept of approximate. 10.001 ~ 10.

So, in that sense, I find ~ quite nice actually.

import LongPackage ~ lp
import HugePackage ~ hp
import MassivePackage ~ mp
import GiantPackage ~ gp

:-)

ok -- forget python -- I'm deleting that comment in favor of

lp imports LongPlayingRecords
hp imports HewlettPackard
mp imports MilitaryPolice
gp imports PariForNumberTheory

I didn’t originally like it but so many people seem to naturally want to write this with as I have to say it seems perverse to do anything else. It doesn’t have to be reserved as a keyword or anything since this is a clear syntactic context.

A programming language that _trys_ to let people know what it means !?!

@JeffreySarnoff

A programming language that trys to be better than Python !!!

@neilpanchal Julia is definitely a language that tries to be better than Python in many ways. On the other hand, it quite obviously doesn't go out of it's way to be _different_ than Python where Python got something right. Julia borrows syntactic conventions from Python left and right.

Please adopt the python as. They got it right.

julia> import LightGraphs as lg
ERROR: syntax: extra token "as" after end of expression

Just another $0.02 comment in favor of as that gets away from Python. fwiw, now that I'm using Julia w/some regularity again I miss this. Not as in Python, but as in Clojure (see example), where package scoped namespaces (which are good for disambiguation) can be simplified to make explicit use of libraries much more wieldy. Since I have a preference to avoid using (i.e. wildcard imports) having some syntactic sugar to import Gadlfy as gf w/o the addition of the const gf = line (what I currently do) would be handy.

Yet another comment supporting the as syntax :heart: I wish it really hard for future Julia releases.

I too wish this to happen. Especially since it is not a breaking change (right?).

I think this will be implemented at some point (I would try myself if I could make the time), but in the meantime, the ImportMacros package has pretty much all of the functionality requested here (although it's missing documentation):

@import ModuleA as ma
@using ModuleB as mb
@from ModuleC use long_function_name as func
@from Module use @macroname as @alias

I didn't know about ImportMacros.jl, thanks for pointing it out. If this functionality exists in a package then I guess it's not so important to have it in the base language.

I still think this would be good to have in the language.

Imports are IMO exactly the kind of thing for which you almost _always_ want to defer to the base language conventions and not wrap in macros or add a package dependency. import ... const is a better thing to fallback on, even (especially) if the base language is never extended to make that two step process more concise.

In particular, it is slightly ugly to see

using ImportMacros
@using OtherPackage as ...
@using ThirdPackage as ...

because the non-macro using sticks out.

How about import returning the module/function/whatever it actually brings to scope?
Then you can write
lg = import LightGraphs
baz = import foo.bar
baz, kit, kat = import foo : bar1, bar2, bar3
The only thing that needs to be changed is the import function

I really like @DhruvaSambrani 's solution because it fits with Julia's overall "everything is an expression" approach.

The only thing is that, in order to be consistent, the import expression would still have to preform the side-effect of bringing the full name into scope (maybe not a disaster). It could also be special-cased to not import the full name, but special-casing is gross.

In OCaml (the language with the best modules!), you can do something like:

module LG = LightGraphs

The important difference here is that the qualified name of every module that is compiled with the OCaml program is automatically in scope.

Of course, using import LightGraphs as LG has the benefit that "everyone" already knows it from Python, but my personal taste is that it's better to reuse assignment syntax for something that literally is assignment.

Or a more extensive change would be to introduce a "namespace" as a Type (idk if that's already done). Then import can expand the module/whatever into a namespace and return the namespace. But this will break all Julia code because simply doing import mod_name will not make the mod_name namespace available in the global scope. If one can somehow make the mod_name namespace available to global when import is used without an assignment preceding it, then that isn't a problem.

Or just add a new function import_as_ns(mod_name) :: Namespace which will be a pure function which returns all the functions/modules etc in a single namespace.

it fits with Julia's overall "everything is an expression" approach

However, import and using are special because they are used only for their side effects, and only at the top level.

Incidentally, given that has this issue has been open for a long time, I wonder if triage would be willing to revisit it and make a decision. I would advocate closing, since

  1. import Foo; const Bar = Foo provides a perfect solution when the user doesn't mind Foo in the namespace,
  2. ImportMacros.jl should handle the rest,
  3. neither seems to be used very frequently in practical code to warrant special syntax.

I will say that after all this time, I still miss this (and I also use ImportMacros in my own code).

I do think that the style of coffee you’re writing affects the need (or desire)for this. When I’m writing ML or scientific code, I find I don’t miss it much. When I’ve written other code that uses a number of packages with long names is when I find I use ImportMacros the most.

ImportMacros is sufficient. I just agree with comments that it doesn’t look that clean.

I’m also looking forward to the day when I can use an autoformatter in my editor to sort my imports... but it’s less likely to know what to do with @using...

I also agree with @kmsquire that this isn't that big an issue, and ImportMacros should be enough. But whatever method should be documented so that it doesn't come up again.

Though I'd still like the m=import Module sugar 😅

I didn’t intend for my message to imply that this isn’t a big deal.

I would still prefer a syntax for this.

ImportMacros is sufficient. I just agree with comments that it doesn’t look that clean.

I agree. Also, for:

using ImportMacros
@using OtherPackage as ...

It makes it a bit annoying when presenting the language to newcomers or programming beginners to explain why a given script starts with this inconsistency, as it complicates the flow and shifts the focus of the teaching.

neither seems to be used very frequently in practical code to warrant special syntax

The reason why people don't use ImportMacros a lot in actual code a lot could be due to a tradeoff. For instance, I'd be using import ... as ... almost every time, but likely not at the cost of having to import first another package to do it. More than an additional dependency, it also has an impact in terms of "feel" (and some people like their code to look sleek (especially in python or Julia) 🤷‍♂️ , and this using/@using gives a rather "hacky" vibe to the introduction of a script).

So yeah, "ImportMacros is sufficient". For people that really want to use it. But that's the thing, it's not a vital issue, and people can work without it. Nonetheless, I strongly believe it is typically these little things that make for a language's syntax to stick, hence this monster-thread to add its support to base.

I think that import ... as ... is syntactic sugar that would be definitely worth it, it's really easy to understand and to explain, and especially useful in languages such as Julia in which package tend to have rather long names (obviously, this matters only under the assumption that people do not want to do using ...).

And the fact that Python had it implemented first shouldn't really be relevant; Julia should strive at having the best syntax before trying to distance itself from other languages and/or emphasize its own identity.

Having syntax for this and conventions around popular modules (I'm thinking of how it's always import numpy as np and import matplotlib.pyplot as plt) would also give a concise alternative to doing using SuchAndSuch and relying on exported names (which is something I generally avoided in "library" code).

@DominiqueMakowski:

presenting the language to newcomers or programming beginners to explain why a given script starts with this inconsistency

I don't understand why you think it is an inconsistency, it is simply namespace management, which is a normal part of programming in the large.

Also, renaming modules may not be something that newcomers to Julia would encounter first.

I am not saying that there isn't a use case, but it may be to rare to justify its own keyword. At the moment, searching for using ImportMacros gives <10 unique results on Github.

I'd be using import ... as ... almost every time, but likely not at the cost of having to import first another package to do it

This cuts both ways: if the (very minor) cost of using a ligthweight package like ImportMacros or an extra symbol (import LongFoo; const Foo = LongFoo) is not worth the benefit, then the benefit may not be that large.

Julia should strive at having the best syntax before trying to distance itself from other languages and/or emphasize its own identity.

I am not sure why you think this is a motivation in this discussion.

There are some cases where you might want to use as where the const assignment doesn't quite work:

julia> using Distributed

julia> import Future; const future = Future
Future

julia> Future === Distributed.Future # oops, this is what we wanted
false

julia> Future === future # not what we wanted `Future` to mean
true

Yes, you can work around this, but it's awkward. Given that it's often handy, sometimes needed and that import FooBar as FB is the generally agreed upon "least surprising" syntax, that seems like what we should go with. All this needs is for someone to make a PR implementing it.

New Julia user here.
I come from a mostly R background, using Python as well. I have bad experiences with the package common name space that loading mechanisms like using leads to. The risk of collision is one, but just from a readability perspective, their is a mental burden of constantly thinking about each method's module.

I don't know how complex adding this syntax is? Is it doable for a newbie?

Probably not very easy. It will involve hacking the parser code which is written in Scheme and wiring the change through the AST, potentially touching some C code as well.

It would be a nice feature for avoid function name conflict in multiple modules.

From a discussion on slack, I add some thoughts

I wonder if this will make people less careful with choosing function names and properly adding methods to the correct functions. The fact that, e.g.,

numpy.sin
sympy.sin

are different functions is a surefire way of having to implement two version of your function if you want it to work for both numerical and symbolic inputs. If such namespacing becomes widespread in julia as well due to the convenience, we might miss out on a lot of code reuse? I.e., I'm afraid this might give us less of "This encourages people to work together" as it was put by Lyndon in https://www.oxinabox.net/2020/02/09/whycompositionaljulia.html

@baggepinnen I don't think this changes anything in that regard.

In Julia methods implemented in different modules are not merged by importing both methods into the same namespace.

julia> module Foo
           export sin
           sin(s::String) = uppercase(s)
       end
Main.Foo

julia> sin(1)
0.8414709848078965

julia> using .Foo
WARNING: using Foo.sin in module Main conflicts with an existing identifier.

julia> sin("wat")
ERROR: MethodError: no method matching sin(::String)
Closest candidates are:
  sin(::BigFloat) at mpfr.jl:727
  sin(::Missing) at math.jl:1197
  sin(::Complex{Float16}) at math.jl:1145
  ...
Stacktrace:
 [1] top-level scope at REPL[4]:1
 [2] run_repl(::REPL.AbstractREPL, ::Any) at /build/julia/src/julia-1.5.1/usr/share/julia/stdlib/v1.5/REPL/src/REPL.jl:288

julia> Foo.sin("wat")
"WAT"

One has to explicitly add a dispatch to the original function for this to work:

julia> Base.sin(s::String) = Foo.sin(s)

julia> sin("wat")
"WAT"

As far as I can tell, there is no possible way for module aliasing to change how functions are dispatched.

And honestly, some functions with the same name _should_ live in separate namespaces. A find_delta function in a numeric library is going to do something unrelated to to find_delta in a file diff library. Once your code is so polymorphic that it will find a way to execute on broken input, you're getting into JavaScript territory, and nobody wants that.

@ninjaaron I might have failed to convey some nuance in my concern above. I am not concerned with this change changing anything other than the strategy people choose to take when they implement libraries.
The person implementing Foo encountering a warning like

julia> using .Foo
WARNING: using Foo.sin in module Main conflicts with an existing identifier.

can choose to either simply import .Foo.sin as sin2, or think about whether or not he really wanted to provide a new dispatch for Base.sin. My point was that if it becomes very easy to simply consider them different functions, maybe this will become too widespread, even in situations when they really should be different methods of the same function. The current situation, where it's slightly more awkward to deal with different functions with the same name has the nice side effect that people talk to each other and do their best to figure out if it really is the same function or not. I'm not arguing against the possibility to have different functions with the same name, which of course can be very useful. I'm not even sure whether my concern is important or not, but figured it was worth it to lift it to ensure it has been considered.

@baggepinnen That makes sense, and there's no harm in bringing it up. I don't think this will make a huge difference for people creating libraries, since module aliasing will only affect library users. I guess it is possible that having easier module aliasing will result in fewer users _complaining_ about naming conflicts, but I would be surprised if that has a tremendous impact on APIs.

When I write a library, I'm usually pretty conscious of whether I want to add a dispatch to a function in Base or I want to keep it separate. I would assume most other library authors are as well.

@ninjaaron I think if the current convention is using specific implementation for dispatch like

numpy.sin
sympy.sin

using import numpy.sin as np_sin as an extra option for user/developer should not affect the current code base.
The only concern will only be affecting the expose function/interface.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TotalVerb picture TotalVerb  ·  3Comments

musm picture musm  ·  3Comments

omus picture omus  ·  3Comments

tkoolen picture tkoolen  ·  3Comments

iamed2 picture iamed2  ·  3Comments