On Wednesday, April 29, 2015 at 4:41:38 PM UTC-4, Stefan Karpinski wrote: > > 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 Foo > export f > immutable F end > f(::F) = "this is Foo" > end > > module Bar > export f > immutable B end > f(::B) = "this is Bar" > end > > julia> using Foo, Bar > > julia> 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? module Sup > export f > f(::Void) = nothing # declare generic function without a la #8283 > <https://github.com/JuliaLang/julia/issues/8283> > end > > module Foo > export f > import Sup: f > immutable F end > f(::F) = "this is Foo" > end > > module Bar > export f > import Sup: f > immutable B end > f(::B) = "this is Bar" > end > > julia> using Foo, Bar > > julia> f(rand(Bool) ? Foo.F() : Bar.B()) > > > Why is this ok, while the previous code was problematic? Here you can say > which `f` is called: Foo and Bar share `f` so the answer is well-defined – > `f` is always `Sup.f`. > > On Wed, Apr 29, 2015 at 3:36 PM, Tom Breloff <[email protected] > <javascript:>> wrote: > >> Stefan: I agree that typing Distributions.Normal(0,1) in the REPL when >> you're just doing data exploration is really frustrating. However, when >> you're building a package or a larger system, it can be really important to >> either explicitly qualify your types/functions or to explicitly "import >> Distributions: Normal". Frequently I'll be looking at some code within a >> large package with a call to "Normal", but I don't know what function will >> be called here. If I do some type of explicit importing, then I can do a >> simple find command on my directory to figure out what package it may have >> been defined in. If I did a "using", then I have a lot more searching to >> figure out the correct function. Not to mention the silent overwriting of >> functions by "using" different modules can cause unexpected problems. For >> that reason, I try to keep "using" to an absolute minimum in most of my >> code. >> >> On Wednesday, April 29, 2015 at 2:49:34 PM UTC-4, Stefan Karpinski wrote: >>> >>> This scheme seems overly focused on object-oriented programming styles >>> at the cost of making other programming styles much more inconvenient. In >>> an o-o language that might be fine, but non-o-o styles are quite common in >>> Julia. The `connect` example keeps coming up because it is one of those >>> cases where o-o works well. That is not the norm in numerical package, >>> however. This proposal would, for example, make using Distributions >>> <https://github.com/JuliaStats/Distributions.jl> a nightmare – you'd >>> have to explicitly import almost everything that it exports >>> <https://github.com/JuliaStats/Distributions.jl/blob/dacb401cfcfc7463f69af0009497960038b25dd5/src/Distributions.jl#L18-L238> >>> to >>> use. That includes type constructors for distributions etc., since those >>> are themselves (basically) generic functions and their arguments are just >>> built-ins. Instead of doing this: >>> >>> using Distributions >>> X = Normal(0.0, 1.0) >>> p = pdf(X, 0.1) >>> >>> >>> you'd have to do this: >>> >>> using Distributions >>> X = Distributions.Normal(0.0, 1.0) >>> p = Distributions.pdf(X, 0.1) >>> >>> >>> 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. >>> >>> On Wed, Apr 29, 2015 at 2:06 PM, Michael Francis <[email protected]> >>> wrote: >>> >>>> I would expect the user to explicitly import those method, I did not >>>> preclude their existence. And it would be quite reasonable to support the >>>> existing import all syntax hence >>>> >>>> using MyModule >>>> ^ imports only those functions which explicitly reference user types >>>> defined in the module >>>> importall MyModule.Extensions >>>> ^imports the additional functionality on base types >>>> >>>> if I subsequently import another function which conflicts then we throw >>>> an error. This would mean that the vast majority of non conflicting >>>> functions can be trivially exported and used without a namespace qualifier >>>> and extensions to base types would also work, but with the name collision >>>> check in place. >>>> >>>> I don't believe this violates the expression problem ? >>>> >>>> >>>> On Wednesday, April 29, 2015 at 1:55:14 PM UTC-4, Stefan Karpinski >>>> wrote: >>>>> >>>>> >>>>> 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. >>>>>> >>>>> >>>>> Unless I'm misunderstanding, this is a very limiting restriction. It >>>>> would mean, for example, that you can't define and export a generic >>>>> square(::Number) function. That's a silly example, but it's completely >>>>> standard for packages to export new functions that operate on >>>>> pre-existing >>>>> types that don't dispatch on any type that "belongs" to the exporting >>>>> module. >>>>> >>>>> Another way of looking at this is that such a restriction would >>>>> prevent solving half of the expression problem >>>>> <http://en.wikipedia.org/wiki/Expression_problem>. In object-oriented >>>>> languages, extending existing operations to new types is easily done via >>>>> subtyping, but adding new operations to existing types is awkward or >>>>> impossible. In functional languages, adding new operations to existing >>>>> types is easy, but extending existing operations to new types is awkward >>>>> or >>>>> impossible. Multiple dispatch lets you do both easily and intuitively – >>>>> so >>>>> much so that people can easily forget why the expression problem was a >>>>> problem in the first place. Preventing the export of new functions >>>>> operating only on existing types would hobble the language, making it no >>>>> more expressive than traditional object-oriented languages. >>>>> >>>> >>> >
