Julia: Add export-like keyword for private names in a module

Created on 30 Nov 2018  路  10Comments  路  Source: JuliaLang/julia

Currently, we don't have a way to make names private. The convention is to prepend _ to the name. But this is just a _convention_, which users can and do ignore.

Omitting how and why the inability to make names private can ruin everything in large codebases, I propose to add private keyword for making names private that is analogous to export. So something, which currently written as

module Mod

export a, b

a(x) = _a(x)
b(x) = _b(x)

_a(x) = ... # internal implementation
_b(x) = ... # internal implementation
end

would be written as

module Mod

export a, b
private c, d

a(x) = c(x)
b(x) = d(x)

c(x) = ... # internal implementation
d(x) = ... # internal implementation
end

and c and d would not be accessible outside the module. This also makes the code more clean and readable (without hairy names starting with _), which is something that Julia generally strives for. This way we would have 3 kinds of names:

  • truly public names (i.e. exported)
  • public names that are accessible with module name (default option)
  • private names that are not accessible outside the module

I have no knowledge of compiler, but I would expect that this can also enable some compiler optimizations that otherwise are not possible.

modules

Most helpful comment

If you really want to make something private, you already can using let:
```jl
julia> module M
let x = 1
global f() = x
end
end
Main.M

julia> M.f()
1

julia> M.x
ERROR: UndefVarError: x not defined

All 10 comments

We could also use internal, protect, protected, hide or hidden instead of private.

Related: #12069

An alternative design is to require an explicit global annotation in order for a name to be externally accessible via M.x; that effectively makes private the default. Of course, it would be breaking unless it was opt in.

Another thought I've had is that "private globals" should probably be private to a given file. When you use include to include a lot of files it's a bit easy to accidentally have name collisions across files that are totally unrelated.

An alternative design is to require an explicit global annotation in order for a name to be externally accessible via M.x

That could similarly be accomplished with export M.x, to signify that it's public but the intended API for it is to qualify it with the module name, e.g. CSV.read. Unless you mean that it would not be accessible at all without a global annotation. That would make unit testing internals a bit harder.

Unless you mean that it would not be accessible at all without a global annotation. That would make unit testing internals a bit harder.

That was what I meant, but you're right that this would be kind of annoying for testing stuff.

Having a way to access the private bindings would be Julian. In #12069 using a..b was suggested.

Since making binding private has a cost of making things such as unit testing more difficult, I think it should not be the default option. However, I think if the user really, really wants to make a binding private (i.e. inaccessible outside the module) he should be able to do so by explicitly writing private or something.

If you really want to make something private, you already can using let:
```jl
julia> module M
let x = 1
global f() = x
end
end
Main.M

julia> M.f()
1

julia> M.x
ERROR: UndefVarError: x not defined

A very simple approach could be to have a nested module:

module A
module __private
#isolate this stuff
end
#less private stuff
end

In order to increase convenience, one would probably want some macro @import_all_from_module, in order to permit unqualified access to the __private namespace inside of A. Then we'd have

module A
module __private
@import_all_from_module ..
#isolate this stuff
end
@import_all_from_module __private
#less private stuff
end

Re really preventing access: I think this is a very bad idea. We should serve the library user, not the library author. The concrete desires of the caller override the imagination of the callee, at least unless we want to perform optimizations that rely on "hidden internals" being inaccessible (like e.g. pointer_from_objref for bitstypes).

Was this page helpful?
0 / 5 - 0 ratings