Open eliasvasylenko opened 4 years ago
Tracking internally as Issue GR-20936.
Thanks a lot for this suggestion. Sorry for coming back to this so late.
I agree being able to define the mustExport relationship seems useful. Within messages of a library one can express this relationship already with @Abstract(ifExported="otherMessage")
I am not sure about the implementation of:
conses.as(DataLibrary.class).someCommonOperation(cons)
And the advantages it has over using:
@Specialization(guards = "conses.isCons(cons)", limit = "3")
public static Object doDefault(
Object cons,
@CachedLibrary("cons") ConsLibrary conses,
@CachedLibrary("cons") DataLibrary data) {
return data.someCommonOperation(cons);
}
Could you elaborate? I can see one advantage with your suggestion, namely that in this case the library instance used internally to export messages in ConsLibrary could be reused also outside at the call site. As the as
method could just return the same instance internally used. I only don't see this happen very often. But if it turns out to happen, we can implement that internally. We can automatically lookup a DataLibrary in ConsLibrary if necessary (we know they are related because their receiver expression is the same).
The reason why we not try to split libraries in many small libraries, but keep one big library for interop is that it is a hard trade-off in terms of footprint. Using many small libraries requires many small instances of cached libraries. While with the one big library approach you only need to use one. Also writing generic operations really becomes a pain with small libraries.
My recommendation of structuring libraries is one library for interop and one library for the language.
Not at all, thanks for the response. And sorry for stuffing so much content into a single issue, but I couldn't think of any good way to break it down since it was more of an "exploration of alternatives" than a solid proposal.
I am not sure about the implementation of: conses.as(DataLibrary.class).someCommonOperation(cons) And the advantages it has over using:
Well I just figured that if we statically enforce that an implementation of ConsLibrary
must also export DataLibrary
then we should also be able to statically furnish the latter with a reference to the former (or indeed have it be the same reference). I thought that it seems a bit redundant to be doubling up on the caching machinary that's all, but ...
Could you elaborate? I can see one advantage with your suggestion, namely that in this case the library instance used internally to export messages in ConsLibrary could be reused also outside at the call site. As the as method could just return the same instance internally used. I only don't see this happen very often. But if it turns out to happen, we can implement that internally. We can automatically lookup a DataLibrary in ConsLibrary if necessary (we know they are related because their receiver expression is the same).
Yes you're right, my little API suggestion is unnecessary since you can just make the same optimisation transparently by recognising that the receiver expressions are the same. Your way is better.
My recommendation of structuring libraries is one library for interop and one library for the language.
Right, the "grouping" option is what I've gone with so far too as it feels like the best compromise.
But if we group then the problem still remains that it's difficult to share fast code. Defaults in the @GenerateLibrary
class can't specialize, and @ExportLibrary
classes can't be abstract, so there appears to be no way to share message specializations between library exporters.
Say we have some class or interface AbstractCons
, which we want to contain some specialized message exports to share between implementations, but which is itself incomplete. It would be nice if we could annotate the class with @Abstract
, or omit the @ExportLibrary
annotation, then have some other class ConsPair extends AbstractCons
inherit those message exports and export any remaining messages.
I think there are some missing pieces that would make using libraries a little easier and require less repetition. For the sake of example, consider an imaginary Lisp dialect where the only primitives are
Cons
andSymbol
, which are bothData
, and which have some common behaviour between them (e.g. aconsOnto
message).There is a choice when designing libraries between "grouping" and "splitting"; do we create one big
DataLibrary
with lots ofisData
,isCons
, andisSymbol
guard messages for specific types, or do we split them into lots of separateConsLibrary
andSymbolLibrary
etc? Either way it seems not to be possible to declare relationships like "every cons cell must also exportInteropLibrary
", with a default implementation for interop array messages based on traversing the cons structure as a list.If we group, then we get the benefit of being able to declare which messages require the exporting of other messages using
@Abstract
, and we only have to inject a single@CachedLibrary
to deal with an instance in a specialisation, but there are limited options for specifying fast default behaviour on a per-type basis since A) default message implementations can't specialize and B) exporters of a library can't be conceptually abstract, i.e. they must export all messages of the library.If we split, we now can write better-performing default exports since we're assuming a more specific type and the availability of certain message implementations, but if there is a hierarchical relationship between libraries we can't express this, we just have to try to remember that every exporter of e.g.
IntLibrary
must also exportConsLibrary
,DataLibrary
andInteropLibrary
. We also may have to inject a bunch of extra@CachedLibrary
side-by-side into our specialisations.In other words, I'm suggesting to enhance the way in which we can express hierarchy between libraries and share parts of their implementation. The simplest way to address some of this seems to be to allow the
@Abstract
annotation to apply to types instead of just methods.Another option is to allow something similar to the following:
With a new method on Library to get instances of our required exports:
Then every annotation of
@ExportLibrary(ConsLibrary.class)
must be accompanied by exports of the other libraries.