Encapsulation of project with private keyword #7088
Replies: 3 comments 10 replies
-
What if we allowed Constructors/fields are made private when we don't want the application programmer access, because that would put the burden on them to maintain the invariants of the time. There are usually two cases:
Allowing individual fields to be private is half-way between these two. Open types are simple to create and simple to use, and we use them unless we have a reason not to. Open types have a clear interface: you can put anything here, and you can read it afterwards. A partially-closed type is more confusing for the user; they have to understand the internals of the type to understand why certain fields are private. In a fully closed type, smart constructors and others methods define the interface. I think the syntax is a little awkward. I understand why it makes sense to not require parentheses for a keyword, but it still reads initially like there is a field called
The elegance of the constructor format, borrowed from languages like Haskell, works in part because modifiers (such as visibility) are elsewhere in the file, which leaves the constructor in a form that's easy to read. |
Beta Was this translation helpful? Give feedback.
-
This whole discussion was added to the repo documentation in a30993a |
Beta Was this translation helpful? Give feedback.
-
I have just created these tasks that track the progress of implementation:
The implementation order of these issues is to be specified. |
Beta Was this translation helpful? Give feedback.
-
Motivation - encapsulation
Currently, all the entities (modules, types, methods, constructors, fields) from a project/library can be imported and used, even the entities that are meant to be internal to the library. For example,
Standard.Table.Internal.Vector_Builder.Vector_Builder
can be easily imported and used with:One has to explicitly import this internal type with the aforementioned import statement, it cannot be imported directly from
Standard.Table
library byfrom Standard.Table import Vector_Builder
, as it is not exported from the library'sMain
module. But it can be imported and used nonetheless. We could also skip the import statement, and use it directly via FQN.We want to be strict about this and forbid users any kind of usage of internal entities.
Requirements
from Library import all
.from Library import Symbol_1, Symbol_2, ...
.import Library.Public_Module.Public_Type
.Library.Public_Module.Public_Type
.Proposals that were already dismissed
There has been a long internal discussion in the Engine and Libs team. So far, two proposals and one prototype have been made:
The Pavel's proposal was dismissed because it clashes with Wojciech's vision of everything being public by default and private on demand, and also because it is unnecessarily difficult to expose an entity.
Jaroslav's proposal was dismissed by Wojciech after they had a chat about it.
In this discussion, let's only discuss the syntax and semantics of the
private
keyword that shall be added to the language, as suggested by @wdanilo. To better understand the dismissed proposals, see the links.Introduction of private keyword
Let's introduce a
private
keyword. By prepending (syntax rules discussed below)private
keyword` to an entity, we declare it as project private. A project-private entity is an entity that can be imported and used in the same project, but cannot be imported nor used in different projects. Note that it is not desirable to declare the entities as module private, as that would be too restrictive, and would prevent library authors using the entity within the project.From now on, let's consider project-private and private synonymous, and public as an entity that is not private.
Syntax
All the entities, except modules, shall be declared private by prepending them with
private
keyword. Declaring a module as private shall be done be writing theprivate
keyword at the very beginning of the module, before all the import statements, ignoring all the comments before. Fields cannot haveprivate
keyword, only constructors.Semantics
Submodules
A hierarchy of submodules cannot mix public and private modules. In other words, if a module is public, its whole subtree must be public as well. For example, having a public module
A
and private submoduleA.B
is forbidden and shall be reported as an error during compilation.Example
For an example, let's consider a few sources in a simple library
Lib
and its usage from a temporary sourcetmp.enso
. Compiler and runtime failures are described in comments in the code snippets.Lib/src/Pub_Type.enso:
Lib/src/Methods.enso:
Lib/src/Internal/Helpers.enso:
Lib/src/Main.enso:
tmp.enso:
Checks
There shall be two checks. One check during compilation, that can be implemented as a separate compiler pass, and that will ensure that no private entity is re-exported (exported from a module that is different from the module inside which the entity is defined) and that for every type it holds that either all the constructors are public or all the constructors are private
The second check shall be done during the method/name resolution step. This step happens at runtime, before a method is called. After the method is resolved, there shall be no further checks, so that the peak performance is not affected.
Performance impact
The performance hit on compilation time is minimal, as there are already dozens of different compiler passes. Moreover, in the new compiler pass we shall check only imports and exports statements, no other IR.
The performance hit on runtime, during method resolution, is minimal as well, because it can be as easy as additional lookup in a hash map. Peak performance will not be affected at all, as there are no further checks after method resolution.
Overcoming encapsulation
Sometimes it is useful to be able to access internal entities. Testing is the most obvious example. Let's introduce a new CLI flag to the Engine launcher called
--disable-private-check
, which will disable all the private checks during compilation and method resolution.Other notes
Edit (2023-07-17)
Based on @GregoryTravis's observation in https://github.com/orgs/enso-org/discussions/7088#discussioncomment-6233948: All the types shall be either open (all constructors and all fields are public) or closed (all constructors and all fields are private), but nothing in between.
The related changes to the specification:
private
keyword for fields.cc @jdunkerley @radeusgd @GregoryTravis @JaroslavTulach @hubertp @4e6
Beta Was this translation helpful? Give feedback.
All reactions