Bryan Atsatt wrote:
Stanley M. Ho wrote:
Hi Bryan,
Bryan Atsatt wrote:
...
By having Dependency extend Query, instances can be directly passed to
Repository.find(); no conversion required. We could obviously just make
a getter for the Query if we want, but why not make this convenient to
use?
An import dependency describes the relationship between two modules.
Agreed. And I think we would be far better off thinking of and modeling
this relationship as a general *requirement specification*, rather than
using the very narrow definition:
name + version(s)
Clearly these are important elements of an import specification. But the
design should allow for and even support others.
One concrete example is attributes. What is the point of declaring them
on a module if they cannot be used in an import specification?
Sorry, I meant to change this before sending. I do know that you meant
for attributes to be used by higher level mechanisms (e.g.
ServiceLoader). And that is certainly a valid use case (though it isn't
entirely clear to me how this works other than by polling).
But enabling them as import specifiers seems like an even more natural
use case.
And I think these could be very valuable.
The term constraint makes sense when you think of name as the primary
specification, and everything else as modifiers of it.
But this puts all the emphasis on the wrong syLLable, IMO. What will we
do when a contract model is introduced, where attributes and
version(s) are all that is required to specify the desired contract?
Module name won't be required at all in such a specification.
So far, we have three distinct actors in the resolution process:
1. Developer: creates import specifications, using whatever selection
criteria makes sense.
2. Admin: creates import specification *filter* (ImportOverridePolicy),
mapping developer specification to fit the local environment. (Can also
use a VisibilityPolicy, but it isn't clear to me why that same
functionality can't be achieved in the override policy.)
3. Module System: locates definitions that match specifications.
All of these actors collaborate to produce a single list of candidate
definitions at runtime. If that list contains duplicate packages, the
module system must select the best fit, or fail.
Clearly the Query mechanism provides an extensible solution to the
problem in #3. And annotations give us an extensible solution to the
problem in #1.
But, just as clearly, we have a big gap in the middle: the filter model
is not a general, extensible mechanism. And I think we need to fix this.
It seems to me that the best solution is to use the *same* model to
filter as we use for lookup: Query.
(Please see the Query optimization thread for my proposal to change
Query from an opaque to a transparent type.)
So, rather than pass VersionConstraint instances through the narrow()
method, we can pass Query instances. Yes, this does make it a bit more
work to implement such a filter, but it is then a completely extensible,
symmetric model.
And this is also why I proposed that we change ImportDependency to
simply take a Query as a ctor argument:
public ImportDependency (Query spec,
boolean optional,
boolean reExport) {...}
(I should have sent my optimization proposal before I suggested the
above change, so it would've made more sense. Really this would be so
much easier if you could just read my mind :^)
Further, I don't understand the need to limit the filter to only
narrowing operations. For example, why shouldn't an admin be able to
map a module *name* as well? This could be really handy (and might even
allow for elimination of the refactoring/name problem).
In fact, why not allow the admin to be able to filter *any* part of the
import specification? I would prefer to see a more generic model than
the current ImportOverridePolicy, for example:
public class ImportOverridePolicy {
public ListImportDependency getImports(ModuleDefinition def) {
return def.getImportDependencies();
}
public static ImportOverridePolicy getPolicy() {...}
static void setPolicy(ImportOverridePolicy policy) {...}
}
The default implementation does nothing, but subtypes can perform any
transformation they want. The ModuleSystem just calls this method
instead of calling the definition directly.
It is true that we may want to query a module described in the import
dependency, but an import dependency itself is not a query. In fact,
someone may even want to query a module which contains the specific
import dependency, although I think this use case is very rare. Anyway,
it sounded like the main benefit to have import dependency extend query
is to make it easier to pass into Repository.find(). Unfortunately, once
a class is extended from another, all the public/protected
methods/fields from the parent class would automatically become part of
the signature of the child class.
No kidding? :^)
I don't