*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.

Fortunately, I am certain that this is not the crux of the problem...  And 
I agree completely with Stefan: Limiting exports to this particular case is 
extremely restrictive (and unnecessary).  I also agree that, this 
restriction *would* keep developers from developing very useful monkey 
patches (among other things).  So let's look at the problem differently...

*Problem 1: A module "owns" its verbs.*
See discussion above discussion (
https://groups.google.com/d/msg/julia-users/sk8Gxq7ws3w/ASFlqZmVwYsJ) if 
you are not familiar with the idea.

*Problem 2: Julia has 2 symbol types (for objects/methods/...)*
Well, this is not really a problem... It is terrific!  Julia's 
multi-dispatch engine allows us to overload methods in a way I have never 
seen before!

In fact, the multi-dispatch system allows programmers to DO AWAY WITH 
namespaces altogether for the new type of symbol (at least to a first 
order).

*So what are the symbol types?*
1) Conventional Type: Used in most other imperative languages
2) Multi-Dispatch Type: Symbols of methods whose signature can be uniquely 
identified.

By this definition, if a symbol is not associated with a unique signature, 
it is simply a conventional symbol.

And, as defined, conventional symbols run a high potential for "signature 
collisions"... because the signature is insufficient to uniquely identify 
whether we are referring to Foo.CommonSymbol(???) or Bar.CommonSymbol(???).

*So why do we need namespaces?*
Namespaces were created to let programmers use succinct symbol names 
without the problem of running into never-ending name collisions.  Instead 
of dealing with symbol bloat 
(Foo.FoosSpecialSymbolThatCannotCollideWithAnybody) - we use scopes to give 
symbols a nice hierarchy (namespaces are basically named scopes).  When 
writing code in the native scope, all symbols are nice and short... and you 
can even *import* the symbol names to your own scope to interact with the 
module.  Now the user gets to use short names - not just the module 
developer!

*So what about the multi-dispatch-ed symbols (type 2)?*
Technically, they could *all* be located at the global scope.  You don't 
really need to say Foo.run... because the call signature is unique (by 
definition) - so there is no ambiguity.

*Ok, then where is the problem for module developers?*
Simply put: Julia is trying to cram those beautiful multi-dispatchable 
methods into a construct (namespaces) that *is not technically needed* for 
type 2 symbols.  As a consequence, the current Julia implementation 
actually makes it *difficult* for module developers to use multi-dispatch.  
Of course, everything is just peachy when you work from within Base :).

Unfortunately, I believe this has caused a little more collateral damage: 
Since it is easier to work within Base, it has become this sort of "god 
module".  I believe Jeff has mentioned developers are getting reluctant to 
make it grow any further.




*The Distributions module: Can it remain elegant?*
The short answer is yes :)!

Stefan has a valid concern: you either have to keep using full symbol paths 
(Distributions.Normal), or
"[...]
you'd have to explicitly import every Distributions type and generic stats 
function that you want to use. Instead of being able to write `using 
Distributions` and suddenly having all of the stats stuff you might want 
available easily, you'd have to keep qualifying everything or explicitly 
importing it. Both suck for interactive usage - and frankly even for 
non-interactive usage, qualifying or explicitly importing nearly every name 
you use is a massive and unnecessary hassle.
"

I agree.  We don't want this... what a pain!  Let's not go there.

*So where do we start?*
In this case, "Normal" is a relatively common name, and I cannot say its 
argument list is sufficiently unique to qualify it as a type 2 method.  The 
signature only involves two "::Number" arguments.

Good rule of thumb: If an argument list is made up solely from base types 
(number/char/string/...) is likely to collide with a function in another 
module (say, a Geometry module).

That was easy!: That means "Normal" is a type 1 symbol.  As such, Normal 
*should* be "export"-ed by module Distributions... Nothing changes in the 
implementation!

*Now how do we generate numbers with a normal distribution?*
The Distributions package includes a rand() function.  Interestingly 
enough, the signature for rand makes it a type 2 method - and so it has a 
very low probability of signature collisions.

Here is a simplified definition of the rand function (Not using 
UnivariateDistribution, ...):
import Base: [...] rand
rand(x::Normal, n::Int) = ...
Basically: Distributions is extending Base.rand

With this infrastructure, you can generate random distributions with very 
nice code:
using Distributions
X = Normal(0.0, 1.0)
values = rand(X, 3)

*So what is the problem here?*
Again, it is quite subtle: In Julia, the first module to define a method 
essentially becomes the "owner" (in this case "Base").  This is awkward.

*Then what could be done differently?*
Well, one possible solution would be simply declare type 2 methods as 
"global":
global rand(x::Normal, n::Int) = ...
(tentative syntax)

Due to its unique signature, this type 2 method is unlikely to collide with 
the definition from another module anyways... so why not make it global?

NOTE: I don't think this is ideal, but it is much better than the current 
solution where a module "owns" a given symbol (or verb, if you will).

*Ok, but does this actually help?*
Surprisingly yes!  By not using the same hammer for type 1 & type 2 
symbols, *module developers* get a means to push the safe type 2 symbols to 
the global namespace... and *module users* get to decide when/where to 
import type 1 symbols from a given library.

Consequence 1:
It will be very unlikely that two modules will trigger signature 
collisions.  For example: draw(PlotPackage1.Canvas, ...) does not collide 
with draw(PlotPackage2.Canvas, ...).

Consequence 2:
Even in any other programming language out there: you expect that type 1 
symbol collisions be fatal (because they *are* ambiguous).  As usual, you 
can only import symbols ("using") from *a single* conflicting module to the 
current namespace at a time.  The other import *must* be a true "import".

...But that's ok... because you still get to use Julia's awesome 
multi-dispatch engine on all the type 2 symbols!!!

>>>And that's the *biggest* problem with Julia's current behavior.  You 
don't even get to use multi-dispatch on type 2 symbols when you do an 
"import".  You only get to reap the benefits when "using" succeeds - or if 
you import individual type 2 methods.<<<

Reply via email to