You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is from the point of view of using component composition for system design where dynamic linking / composing components is key.
Issue: Asymmetrical import/export resolution
Transitive import/export dependencies are resolved inconsistently for imports and exports:
Imports dependencies are resolved as additional imports.
Exports dependencies are resolved as additional imports.
This causes worlds with symmetrical definitions to be resolved asymmetrical.
While both worlds look like they would compose perfectly together, once resolved they don't and instead leave a gap behind.
This would resolve into these worlds on the components:
/// Component that want to use validationworldservice {
importtypes;
importvalidation;
}
/// Component that provides validationworldvalidator {
importtypes;
exportvalidation;
}
While the validation interface can be composed, the types interface cannot be composed because it is imported by both components.
This would require an explicit export or a third component or the host to provide these types.
The obvious solution would be to update the resolution to be symmetrical:
Imports dependencies are resolved as additional imports.
Exports dependencies are resolved as additional exports.
But this isn't a valuable solution either as it forces the exporter to implement the types even tho it has no use to do so.
Solution: Fix inconsistent differentiation between values and signatures (interfaces)
For the sake of less confusion I will call the classic interfaces/traits "signatures"
Wit types can be split into 2 categories.
These categories have different uses cases for wit itself.
Please correct me if I'm wrong
Category
type
Compilation
Runtime
Values
string, u32, record, variant, ...
✅
❌
Signatures
resource, function
✅
✅
For the runtime, Values don't really matter as the actual implementation is added automatically and doesn't need to be imported or exported.
For the runtime, Signatures matter as the actual implementation is not added automatically and needs to be either imported or implemented to be exported.
/// Validate provided graphs for correctnessinterfacevalidation {
usetypes.{start, end};
// use types.{path-graph};
use import types.{path-graph}; // <--- The validator only wants the signature to validate the graph and doesn't provide the implementation.
validate: func(graph: path-graph) -> bool;
validate-path: func(start: start, end: end) -> bool;
}
/// Component that want to use validationworldservice { // <--- Has to export the path-graphimportvalidation;
}
/// Component that provides validationworldvalidator { // <--- Will import the path-graphexportvalidation;
}
This would resolve into these worlds on the components:
/// Component that want to use validationworldservice {
usetypes.{start, end, path};
use exporttypes.{path-graph};
importvalidation;
}
/// Component that provides validationworldvalidator {
usetypes.{start, end, path};
use importtypes.{path-graph};
exportvalidation;
}
Summary
With these changes interfaces are more expressive in what they require / provide and are resolved symmetrical.
I just want to share my point of view from using wit and it's interfaces as contracts in general where a symmetrical definition is important. I'm open for any discussion :)
Benefits
Symmetrical worlds
Symmetrical import/export resolution
More expressive interfaces
Easier component composition
No export/import type mismatches in bindgen for value types
// Before
import types;
// Afterusetypes.{start, end, path};
useimport types.{path-graph}; // <--- Same behaviouruseexport types.{path-graph} with { types = import types };
Example uses in WASI
wasi:http@0.3
interfacetypes {
resourcerequest {}
resourceresponse {}
varianterror-code {}
}
interfacehandler {
usetypes.{error-code};
use import types.{request, response}; // <--- As a request handler i only need the signature of request and response
handle: async func(request: request) -> result<response, error-code>;
}
wasi:keyvalue@0.2
interfacetypes {
resourcebucket {}
varianterror {}
}
interfacestore {
usetypes.{error};
use export types.{bucket}; // <--- As a store i need to provide the implementation of the bucket signature
open: func(identifier: string) -> result<bucket, error>;
}
This is from the point of view of using component composition for system design where dynamic linking / composing components is key.
Issue: Asymmetrical import/export resolution
Transitive import/export dependencies are resolved inconsistently for imports and exports:
This causes worlds with symmetrical definitions to be resolved asymmetrical.
While both worlds look like they would compose perfectly together, once resolved they don't and instead leave a gap behind.
Example:
This would resolve into these worlds on the components:
While the
validationinterface can be composed, thetypesinterface cannot be composed because it is imported by both components.This would require an explicit export or a third component or the host to provide these
types.The obvious solution would be to update the resolution to be symmetrical:
But this isn't a valuable solution either as it forces the exporter to implement the
typeseven tho it has no use to do so.Solution: Fix inconsistent differentiation between values and signatures (interfaces)
For the sake of less confusion I will call the classic interfaces/traits "signatures"
Wit types can be split into 2 categories.
These categories have different uses cases for wit itself.
Please correct me if I'm wrong
string,u32,record,variant, ...resource,functionFor the runtime, Values don't really matter as the actual implementation is added automatically and doesn't need to be imported or exported.
For the runtime, Signatures matter as the actual implementation is not added automatically and needs to be either imported or implemented to be exported.
So, looking back at the example:
This insight alone doesn't change anything as we also need to rethink the import/export behaviour.
The same definitions can be used for both imports and exports.
It's same for generated code via bindgen (kind of relates to Split 'use' inside worlds into 'use import' and 'use export' #308).
Whenever a signature is taken into scope it needs to be defined if it's an export or import.
This requires changes to the way entries are referenced via the
usedirective.useto reference a valueuse importto import a signature referenceuse exportto export a signature referenceThat's the relation to Split 'use' inside worlds into 'use import' and 'use export' #308
Looking at the example again:
This would resolve into these worlds on the components:
Summary
With these changes interfaces are more expressive in what they require / provide and are resolved symmetrical.
I just want to share my point of view from using wit and it's interfaces as contracts in general where a symmetrical definition is important.
I'm open for any discussion :)
Benefits
usein worlds and interfacesChanges to WIT
exportkeyword tousedirective (world and interface)importkeyword tousedirective (world and interface)export/importonusefor resources and functionsChanges to bindgen (optional)
string,u32,record,variant...)Handling breaking changes
Make
usesyntactical suguar foruse importon resources and functions.translates to
World resolution
Export Side
Import Side
Could be feature gated via adding it by default as import aswell and defing the export to reference the import
Resulting in
Example uses in WASI
wasi:http@0.3
wasi:keyvalue@0.2