Skip to content

User-defined generic types (parametric polymorphism) #543

@Sintrastes

Description

@Sintrastes

Doing a search in the github issues, I have yet to see a ticket where anyone has explicitly asked for this yet.

Apparently, there has been discussion on something similar to this, but from what I can tell this seems like a sufficiently different proposal to warrant a new issue.

Motivation / Example

Consider that you're writing a world to facilitate a generic API for building bindings to user interfaces from a WASM component. In addition to describing the layout tree (so basically a DOM, but more generic than that, as this could potentially target any sort of GUI framework with a hierarchy of layouts and sub-views), we also want to be able to describe how to bind data to the view.

For this framework, we want to be able to have "reactive values" as part of the API. This is different from a stream<T> because in addition to emitting updates to subscribers on changed values, it also gives you access to the "current" value. Something like:

resource reactive-value<T> {
    current-value: func() -> T;
    updates: stream<T>;
}

The issue with this of course being that WIT does not currently support user-defined generics. So currently we would have to monomorphize and provide implementations for any type T we want to use reactive-value with.

resource i32-reactive-value {
    current-value: func() -> i32;
    updates: stream<i32>;
}

resource f32-reactive-value {
    current-value: func() -> f32;
    updates: stream<f32>;
}

resource string-reactive-value {
    current-value: func() -> string;
    updates: stream<string>;
}

...

Not to mention, if a user wants to extend our API with different types of widgets in another WIT package (e.x. maybe an API for a date-time entry widget), they would also have to do this duplication themselves.

Sketch

To solve this problem, I propose updating the syntax of WIT to allow for custom generic types such as reactive-value<T> to be user-definable. For full generality, we would have to add generics to records, variants, resource types, and function types (though generic function types would obviously not be first-class).

Generic types can be used as you'd expect in packages / interfaces / worlds from the perspective of someone writing a wit package, but in the canonical ABI, all user-defined generic types and functions would be monomorphized, as this could be supported even in languages without parametric polymorphism.

For example:

interface widget-binding {
    resource widget<T> {
        update-value: func(T);
        current-value: func() -> T;
    }

    // Potential syntax for a standalone generic function
    bind-data: <T> func(widget: widget<T>, reactive-value: reactive-value<T>);
}

could be monomorphized into something like:

    resource i32-widget {
        update-value: func(i32);
        current-value: func() -> i32;
    }

    resource f32-widget {
        update-value: func(f32);
        current-value: func() -> f32;
    }

    ...

    i32-bind-data: func(widget: widget<i32>, reactive-value: reactive-value<i32>);
    f32-data: func(widget: widget<f32>, reactive-value: reactive-value<f32>);
    
    ...

depending on which specific type arguments are configured to be instantiated (similarly to this, this is something that could be configured in the user's build tools).

Note: If we want to allow nested generics (e.x. reactive-value<reactive-value<T>> -- which I think we should -- we'd have to come up with a different naming scheme for the monomorphized variants. This is just an example.

Additional Benefits

In addition to saving work for developers in not having to manually monomorphize when writing wit definitions, adding support for user-defined generic types / methods in the wit standard also gives third-party codegen tools more data to work with.

For instance, say someone wants to use WIT as a kind of language-agnostic modeling language to define an important data model for a polyglot project. The goal is to use the .wit definition as a single source of truth for the project's data model, which can then (with custom codegen tools) be transformed into idiomatic implementations of this data model in whatever language they wish.

For a language with built-in support for generic types (say Kotlin), something like widget-binding could be translated over fairly directly:

interface WidgetBinding {
    interface Widget<T> {
        fun updateValue(value: T)

        fun currentValue(): T
    }

    fun <T> bindData(widget: Widget<T>, reactiveValue: ReactiveValue<T>)
}

whereas another language with no generic might resort to manually monomorphized variants (or perhaps even type-erased / dynamically typed variants!) in the generated model code, depending on what is the most idiomatic.

While the primary goal in this example is not multi-language interop, it's possible a codegen tool such as this one in this scenario could also generate some high-level glue code in order to translate between the language-specific idiomatic variants, and the monomorphized versions needed by the canonical ABI for interop between different WASM components (e.x. by type reflection, and delegating to the proper monomorphized call based on the type).

In total, I believe this feature would bring great value to the webassembly component ecosystem, as it allows for more expressive contracts between components (such as the aforementioned widget example) to be more easily expressed, and allows for at least some languages with support for parametric polymorphism to generate higher-level idiomatic bindings to a WIT API than otherwise possible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions