# this is mike.jl
# ------------------------------
module Foo
# ------------------------------
importall Basetype FooType end
value(x::FooType) = "Foo::value"get(x::FooType) = "Foo::get"
export value
end
# ------------------------------module Bar
# ------------------------------
importall Base
type BarType end
value(x::BarType) = "Bar::value"get(x::BarType) = "Bar::get"
export value
end
julia> workspace() ; include("mike.jl")
julia> using Foo
julia> value(Foo.FooType())"Foo::value"
julia> using BarWarning: using Bar.value in module Main conflicts with an existing identifier.
julia> value(Bar.BarType())ERROR: `value` has no method matching value(::BarType)
# -----------------------------------------------------
julia> workspace() ; include("mike.jl")
julia> using Foo
julia> using Bar
julia> value(Foo.FooType())ERROR: `value` has no method matching value(::FooType)
julia> value(Bar.BarType())"Bar::value"
# -----------------------------------------------------
julia> workspace() ; include("mike.jl")
julia> using Bar
julia> using Foo
julia> value(Foo.FooType())"Foo::value"
julia> value(Bar.BarType())ERROR: `value` has no method matching value(::BarType)
julia>
julia> workspace() ; include("mike.jl")
julia> using Foo
julia> using Bar
julia> value(Bar.BarType())"Bar::value"
julia> value(Foo.FooType())"Foo::value"
julia>
module SuperSecretBasevalue() = nothing
export valueend
# ------------------------------
module Foo
importall SuperSecretBase
importall Basetype FooType end
value(x::FooType) = "Foo::value"get(x::FooType) = "Foo::get"
export value
end
# ------------------------------
module Bar
importall SuperSecretBase
importall Base
type BarType end
value(x::BarType) = "Bar::value"get(x::BarType) = "Bar::get"
export value
end
module Module1
type Bar end
my( b::Bar ) = 1
export my # fine exports to the global space
end
module Module2
type Foo end
my() = 1
export my # ERROR exporting function which does not reference local type
end
module Module3
type Wow end
my( w::Wow ) = 1
my() = 1
end
export my # Is an ERROR I can not export a function which does not reference a local type
end
module Module4
type Zee end
my( ::Type{Zee} = Zee ) = 1
export my # Works, but I can select against it using multiple dispatch by providing the last arg
end
MY = using Foo.Bar.ReallyLongModuleName
t = MY.Type()
my( t )
using MyMath # Imports only those functions which include types defined in MyMath
import MyMath.* # Imports all other functions defined in MyMath
import MyMath.afunc # Imports one function
import MyOther.afunc # Fails collides with MyMath.afunc
For anyone who isn't following changes to Julia master closely, Jeff closed #4345 yesterday, which addresses one major concern of "programming in the large".I think the other concern about preventing people from intentionally or accidentally monkey-patching is very legitimate as well, but it's way less clear what to do about it. I've contemplated the idea of not allowing a module to add methods to a generic function unless it "owns" the function or one of the argument types, but that feels like such a fussy rule, I don't think it's the right solution. But I haven't come up with anything better either.
I would have thought stopping intentional behaviour is non-Julian, but accidental errors should indeed be limited. Perhaps adding methods to other modules functions needs to explicit.
Stefan, my takeaways from what you are saying are as follows.
1) dynamic dispatch doesn't work without the potential for surprising ambiguities in a mixed namespace environment. The more modules included the worse this gets.
2) A good practice would be to import no functions from modules I don't own into modules I do and explicit qualify all access to external modules from day one. I can't afford to simply break one day.
3) inside my own namespace, modules continue to use exports but I have to implement SuperSecretBase modules managing my function collapses. (Or minimize the use of modules)
4) throwaway scripts can continue to work as before but risk breakage as new functions are exported.
module Foo
export my_new_function, length
import Base.my_new_function
import Base.length
type Bar ; x::Int ; end
length(y::Bar) = 42
my_new_function(y::Bar) = "Thanks for all the fish!"
end
I think this is just a different mindset than the one we've adopted.
In the mindset you describe, there really *ought* to be only one
function with each name, in other words a single global namespace. As
long as all new definitions for a function have disjoint signatures,
there are no conflicts. To deal with conflicts, each module has its
own "view" of a function that resolves conflicts in favor of its
definitions.
This approach has a lot in common with class-based OO. For example in
Python when you say `x.sin()`, the `sin` name belongs to a single
method namespace. Sure there are different namespaces for *top level*
definitions, but not for method names. If you want a different `sin`
method, you need to make a new class, so the `x` part is different.
This corresponds to the requirement you describe of methods
referencing some new type from the same julia module.
Well, that's not how we do things. For us, if two functions have the
same name it's just a cosmetic coincidence, at least initially. In
julia two functions can have the same name but refer to totally
different concepts. For example you can have Base.sin, which computes
the sine of a number, and Transgressions.sin, which implements all
sorts of fun behavior. Say Base only defines sin(x::Float64), and
Transgressions only defines sin(x::String). They're disjoint. However,
if you say
map(sin, [1.0, "sloth", 2pi, "gluttony"])
you can't get both behaviors. You'll get a method error on either the
1.0 or the string. You have to decide which notion of `sin` you mean.
We're not going to automatically merge the two functions.
Then if we
see the same name appearing in multiple packages, we decide if there
is indeed a common interface, and if so move the packages to using it,
e.g. by creating something like StatsBase or maybe adding something to
Base. But we don't want Base to grow much more, if at all.
Generally, conflicting extensions of methods are a natural consequence of allowing packages to evolve independently (which we should anyway). It is unavoidable as the eco-system grows (even if we address such the Images + DataArrays problem by other means). If this coupling over packages cannot be addressed in a scalable way, it would severely influence the future prospect of Julia to become a mainstream language.
I believe they should be able to use both, as long as there aren't any real conflicts, *without* spurious warnings...
The compiler can't determine that there are no conflicts in the case where the method uses a type that is local to the module?
That is the *only* case where I am saying that it should not be necessary to have an "import Base.bar" or "import Foo.bar" if Iwant to export a function and have it available at a higher level, and not have to worry that later on somebody adding bar to Baseor Foo will cause my module to stop working?
What if I had a getsockname function? ;-)That name was just added to Base tonight apparently... so my code would break...Not good!
If I understand correctly (and this is why I said at the very beginning, part of this may be my newness to Julia), then if I have to explicitly reference
A.set_record, it will not work, because it will *not* dispatch to BetterA.set_record...
Is that correct or not?
...
Scott-- yes! If BetterA imports and extends a function from A, it is
exactly the same function object. A.func and BetterA.func will be
identical. Problems only enter if A and BetterA were developed in
total isolation, and the authors just happened to pick the same name.
The way to go here would be to have the DocumentDB module define an
interface, and then both MongoDB and TokuMX extend it. What I don't
get is that elsewhere in your argument, you seem to say that
coordinating and agreeing on an interface is a non-starter. But I just
don't see how you could talk to both MongoDB and TokuMX with the same
code unless they agreed on an interface.
Now consider `connect`. This might be common to both DocumentDB and
SQLDB. Is it similar enough that it should go in an even more abstract
interface GeneralDB? I'm not sure, but I think this is exactly the
kind of interface design process that happens in any OO language. In
Java, you can have a `connect` method whether or not you implement a
`Connectable` interface. But only if you explicitly refer to the
Connectable interface will you be able to work with code that requires
it. I think the same kind of thing is happening here. You can just
write a `connect` function, or you can say `import
Connectable.connect` first, in which case you will be extending the
"public" `connect` function.
Ah, but that is NOT the situation I've been talking about... If the writer of a module wants to have a function that takes ::Any, and is not using any other types that are local to that package, then, from the rules I'd like to see, they *would* have to explicitly import from Base (or whichever module they intended to extend).
#Sample Base module:
module Base
abstract Stream
abstract FileType <: Stream
type BinaryFile <: FileType ...; end
function open(::Type{BinaryFile}, ...)
...
end
export Stream, FileType, BinaryFile
export open
end #Base
#Implement my own socket communication:
module MySocketMod
type MySocket <: Base.Stream ...; end
function open(::Type{MySocket}, ...)
...
end
export MySocket
export open
end #MySocketMod
#Try out the code:
using Base
using MySocketMod #No problem... "open" ambiguities are covered by mulit-dispatch.
#Great! No abiguities:
myfile = open(BinaryFile, ...)
mysocket = open(MySocket, ...)
You, Jeff and Stefan seem to be concerned with different kinds of "ambiguity." Suppose I import `foo(T1, T2)` from module `A` and `foo(T2, T1)` from module `B`. I take you to claim that if I call `foo(x, y)` then, as long as there is no ambiguity which method is appropriate, the compiler should just choose which of `A.foo()` and `B.foo()` has the proper signature. I take you to be concerned with potential ambiguity about which method should apply to a given argument.
I keep getting accused of insisting that every name have only one
meaning. Not at all. When you extend a function there are no
restrictions. The `connect` methods for GlobalDB and SQLDB could
absolutely belong to the same generic function. From there, it's
*nice* if they implement compatible interfaces, but nobody will force
them to.
Scott, I think you're overstating the damage done by a name collision
error. You can't expect to change package versions underneath your
code and have everything keep working. A clear, fairly early error is
one of the *better* outcomes in that case.
In your design, there are *also* situations where an update to a
package causes an error or warning in client code. I'll grant you that
those situations might be rarer, but they're also subtler. The user
might see
Warning: modules A and B conflict over method foo( #= some huge signature =# )
What are you supposed to do about that?
It's worth pointing out that merging functions is currently very
possible; we just don't do it automatically. You can do it manually:
using GlobalDB
using SQLDB
connect(c::GlobalDBMgr, args...) = GlobalDB.connect(c, args...)
connect(c::SQLDBMgr, args...) = SQLDB.connect(c, args...)
This will perform well since such small definitions will usually be inlined.
If people want to experiment, I'd encourage somebody to implement a
function merger using reflection. You could write
const connect = merge(GlobalDB.connect, SQLDB.connect,
conflicts_favor=SQLDB.connect)
I am saying that if I have foo(A.type, T1, T2), in module A, and foo(B.type, T2, T1), I should be able to call foo(myA, myT1, myT2), and foo(myB, myT2, myT1) without anyproblems.
I keep getting accused of insisting that every name have only one
meaning. Not at all. When you extend a function there are no
restrictions. The `connect` methods for GlobalDB and SQLDB could
absolutely belong to the same generic function. From there, it's
*nice* if they implement compatible interfaces, but nobody will force
them to.
Yes, precisely... and I *do* want Julia to protect the user *in that case*.If a module has functions that are potentially ambiguous, then 1) if the module writer intends to extend something, they should do it *explicitly*, exactly as now, and 2) Julia *should* warn when you have "using" package, not just at run-time, IMO.I have *only* been talking about the case where you have functions that the compiler can tell in advance, just by looking locally at your module, by a very simple rule, that they cannot be ambiguous.
Scott
On Monday, April 27, 2015 at 6:40:50 PM UTC-4, ele...@gmail.com wrote:
On Sunday, April 26, 2015 at 8:24:15 PM UTC+10, Scott Jones wrote:Yes, precisely... and I *do* want Julia to protect the user *in that case*.If a module has functions that are potentially ambiguous, then 1) if the module writer intends to extend something, they should do it *explicitly*, exactly as now, and 2) Julia *should* warn when you have "using" package, not just at run-time, IMO.I have *only* been talking about the case where you have functions that the compiler can tell in advance, just by looking locally at your module, by a very simple rule, that they cannot be ambiguous.The issue is that, in the example I gave, the compiler can't tell, just by looking at your module, if that case exists. It has to look at everything else imported and defined in the users program, and IIUC with macros, staged functions and lots of other ways of defining functions that can become an expensive computation, and may need to be delayed to runtime.CheersLexTo me, that case is not as interesting... if you, the writer of the module, want to use some type that is not defined in your module, then the burden should be on you, to explicitly import from the module you wish to extend... (Base, or whatever package/module the types you are using for that function are defined)...
I'm only concerned about having a way that somebody can write a module, and guarantee (by always using a specific type from the module) that the names it wants to export cannot be ambiguous with other methods with the same name.
module Transgressions
Base.sin(x::String) = "Sin in progress: $x"
end
using Transgressions #Doesn't really do anything in this example...
map(sin, [1.0, "sloth", 2pi, "gluttony"])
module Religion
#Name "Transgressions" has a high-likelyhood of name collisions - don't "export":
type Transgressions; name::String; end
#Personally, I find this "Transgressions" example shows that base should *not* "own" sin.
#Multi-dispatch *should* be able to deal with resolving ambiguities...
#In any case, this is my workaround for the moment:
Base.sin(x::Transgressions) = "Sin in progress: $x"
#Let's hope no other module wants to "own" method "absolve"...
absolve(x::Transgressions) = "Sin absolved: $x"
export absolve #Logically should have sin here too... but does not work with Julia model.
end
using Religion
Xgress = Religion.Transgressions #Shorthand... "export"-ing Transgressions susceptible to collisions.
map(sin, [1.0, Xgress("sloth"), 2pi, Xgress("gluttony")])
I made the point at the outset that it isn't hard (or expensive) if the exported functions from a module must reference types defined in that module. Hence the suggestion that module developers should only be able to export functions which reference owned/hard/contained/user types.
using DistributionsX = Normal(0.0, 1.0)p = pdf(X, 0.1)
using Distributions
X = Distributions.Normal(0.0, 1.0)p = Distributions.pdf(X, 0.1)
Imagine you want to do some math and you want to output a pdf from the Pdf, it exports pdf( ... )now you have to qualify every call to pdf in distributions, so you still end up with your worst case.
module Fooexport fimmutable F endf(::F) = "this is Foo"endmodule Barexport fimmutable B endf(::B) = "this is Bar"end
julia> using Foo, Bar
julia> f(rand(Bool) ? Foo.F() : Bar.B()) # which `f` is this?
module Supexport ff(::Void) = nothing # declare generic function without a la #8283endmodule Fooexport fimport Sup: fimmutable F endf(::F) = "this is Foo"endmodule Barexport fimport Sup: fimmutable B endf(::B) = "this is Bar"end
julia> using Foo, Barjulia> f(rand(Bool) ? Foo.F() : Bar.B())
module Supexport ff(::Void) = nothing # declare generic function without a la #8283end
f(::Union()) = nothing
@Stefan: Out of curiosity, do you see any inherent problems in having Julia automatically create such an "empty" function f in the module that imports both Foo.f and Bar.f and then merge the (unambiguous) methods into the automatically created f? (Or just automatically merge unambiguous methods to whatever function already has the name f in the importing module.) Or is it just not in the style of Julia (or in the vision of its creators) to do something like that?
Again, I'm just asking out of curiosity, as I'm finding this conversation an interesting vehicle for learning about issues of scope and naming in Julia.
Tom, this is a very legitimate concern. A simple solution is to have a coding standard not to use `using` when writing packages. Google has created coding standards for both C++ and Python, which are now widely used beyond the company.Automatic function merging goes in the opposite direction: with this feature it becomes impossible to even say which package a function comes from – it's not even a meaningful question anymore. That is the point of Jeff's Base.sin versus Transgression.sin example – map(sin, [1.0, "greed", 2pi, "sloth"]). There is no answer to the question of which of the function Base.sin and Transgreassion.sin the `sin` function refers to – it can only refer to some new `sin` that exists only in the current module and calls either Base.sin or Transgressions.sin depending on the runtime values of its arguments. Perhaps this can be made clearer with an even nastier example, assuming hypothetical code with function merging:module Fooexport fimmutable F endf(::F) = "this is Foo"endmodule Barexport fimmutable B endf(::B) = "this is Bar"endjulia> using Foo, Barjulia> f(rand(Bool) ? Foo.F() : Bar.B()) # which `f` is this?Which `f` is intended to be called here? It cannot be statically determined – it's not well-defined since it depends on the value of rand(Bool). Some dynamic languages are Ok with this kind of thing, but in Julia, the *meaning* of code should be decidable statically even if some of the behavior may be dynamic. Compare with this slightly different version of the above code (works on 0.4-dev):
I think this is easier to solve than you think.The compiler should see this call as having a signature of Union(F,B), which *neither* Foo.f nor Bar.f have...
So, it can either, give an error,
or be even smarter, and see that it should be equivalent to:rand(Bool) ? f(Foo.F()) : f(Bar.B()),which is *totally* determined statically!
What's the real problem here?
On Apr 29, 2015, at 6:19 PM, Stefan Karpinski <ste...@karpinski.org> wrote:On Wed, Apr 29, 2015 at 6:07 PM, Scott Jones <scott.pa...@gmail.com> wrote:I think this is easier to solve than you think.The compiler should see this call as having a signature of Union(F,B), which *neither* Foo.f nor Bar.f have...What does this mean?
So, it can either, give an error,What error? Why? You want a feature which, when used, is an error? Why have the feature in the first place then?
or be even smarter, and see that it should be equivalent to:rand(Bool) ? f(Foo.F()) : f(Bar.B()),which is *totally* determined statically!That trick happens to work in this particular example, but you can easily construct cases where that transformation can't be done. If the argument to f is a function argument, for example. But it still has the exact same problem.
import Base: [...] rand
rand(x::Normal, n::Int) = ...
using Distributions
X = Normal(0.0, 1.0)
values = rand(X, 3)
global rand(x::Normal, n::Int) = ...
module Fooexport fimmutable F endf(::F) = "this is Foo"endmodule Barexport fimmutable B endf(::B) = "this is Bar"end
julia> using Foo, Bar
julia> f(rand(Bool) ? Foo.F() : Bar.B()) # which `f` is this?
ERROR: `f` has no method matching f(::F)
#Sorry: My version of Julia does not have rand(Bool)...
Base.rand(::Type{Bool}) = randbool()
module Foo
export f
immutable F end
f(::F) = "this is Foo"
end
module Bar
#export f #Nope... cannot do this... Foo defined first: it "owns" f
immutable B end
#Sad but true: Foo "owns" f... so we must adhere to this reality:
import Foo
Foo.f(::B) = "this is Bar"
end
using Foo, Bar
#No problem... Julia has an algorithm to dispatch functions
#even if the compiler cannot resolved the call statically.
#Of course, a statically resolved dispatch would be faster than a dynamic one...
for i in 1:10
println(f(rand(Bool) ? Foo.F() : Bar.B()))
end
module MyModule
export foo
type MyType end
foo(x::MyType) = ...
foo(x) = ...
end
internalmethod() = foo(5)
Can anyone point me in the right direction of the files/functions in the core library where dispatch is handled? I'd like to explore a little so I can make comments that account for the relative ease at implementing some of the changes suggested.
I agree that it would be really nice, in some cases, to auto-merge function definitions between namespaces (database connects are very simple OO example). However, if 2 different modules define foo(x::Float64, y::Int), then there should be an error if they're both exported (or if not an error, then at least force qualified access??) Now in my mind, the tricky part comes when a package writer defines:
module MyModule
export foo
type MyType end
foo(x::MyType) = ...
foo(x) = ...
end
module MyModule
const somethingImportant = loadMassiveDatabaseIntoMemory()
_cleanup() = cleanup(somethingImportant)
type MyType end
string(x::MyType) = "MyType{}"
...
end
using Gadfly as G, Winston
import Gadfly, Winston
with Gadfly
plot(x)
end
with Winston
plot(y)
end
Base.sin(x::Real) = fly("Vegas")
Scott and Michael:
I am pretty certain I understand what you are saying, but I find your examples/descriptions a bit confusing. I think (hope) I know why Stafan is confused.
Stefan:
I think Scott has a valid point but I disagree that "exported functions from a module must reference types defined in that module". It is my strong belief that Scott is merely focusing on the symptom instead of the cause.
Your restrictions are making it very hard to develop easy to use APIs that make sense for the people using them…That’s why so many people have been bringing this issue up…
My goal is not to remove namespaces, quite the opposite, for types a namespace is an elegant solution to resolving the ambiguity between different types of the same name. What I do object to is that functions (which are defined against user defined types) are relegated to being second class citizens in the Julia world unless you are developing in Base. For people in Base the world is great, it all just works. For everybody else you either shoe horn your behavior into one of the Base methods by extending it, or you are forced into qualifying names when you don't need to.
1) Didn't that horse already bolt with Base. If Base were subdivided into strict namespaces of functionality then I see this argument, but that isn't the case and everybody would be complaining that they need to type strings.find("string")
2) To me that is what multiple dispatch is all about. I am calling a function and I want the run time to decide which implementation to use, if I wanted to have to qualify all calls to a method I'd be far better off in an OO language.
Stefan,
I am sorry, but my experience leads me to disagree with your statement that Julia is unable to dispatch a function dynamically (@ runtime).
...So your statement confuses me a little...