Skip to content

Support context-generic dependency injection from plain functions #193

@soareschen

Description

@soareschen

The CGP computation macros like #[cgp_computer] can be extended to allow function arguments to be extracted from the context instead of from the arguments.

Current State

Currently we can write:

#[cgp_computer]
pub fn rectangle_area(width: f64, height: f64) -> f64 {
    width * height
}

which would expands to:

#[cgp_impl(new RectangleArea)]
impl<Code> Computer<Code, (f64, f64)> {
    fn compute(
        &self, 
        _code: PhantomData<Code>,
        (width, height): (f64, f64), 
    ) -> f64 {
        rectangle_area(width, height)
    }
}

Dependency Injection in Function

We should be able to also write something like:

#[cgp_computer]
pub fn rectangle_area(
    #[implicit] width: f64,
    #[implicit] height: f64,
) -> f64 {
    width * height
}

which would expands differently to get the dependencies from the context:

#[cgp_impl(new RectangleArea)]
impl<Code> Computer<Code, ()>
where
    Self: HasField<Symbol!("width"), Value = f64>
        + HasField<Symbol!("width"), Value = f64>,
{
    fn compute(
        &self, 
        _code: PhantomData<Code>,
        (width, height): (f64, f64), 
    ) -> f64 {
        rectangle_area(
            self.get_field(PhantomData::<Symbol!("width")>),
            self.get_field(PhantomData::<Symbol!("height")>),
        )
    }
}

Provider Trait Promotion

This would also allow us to later on generate promotion providers to promote a Computer provider to proper traits. For example:

#[cgp_component {
    provider: AreaCalculator,
    derive_promote: PromoteAreaCalculator,
}]
pub trait HasArea {
    fn area(&self) -> f64;
}

This would allow a generated PromoteAreaCalculator provider to wrap the RectangleArea provider to implement AreaCalculator:

pub struct Rectangle {
    pub width: f64,
    pub height: f64,
}

delegate_components! {
    Rectangle {
        AreaCalculatorComponent:
            PromoteAreaCalculator<RectangleArea>,
    }
}

Direct Function Promotion

We can also promote a function directly to implement a provider trait when defining the function, like:

#[cgp_computer(AreaCalculatorComponent)]
pub fn rectangle_area(
    #[field] width: f64,
    #[field] height: f64,
) -> f64 {
    width * height
}

Then it would automatically delegate the component like:

delegate_components! {
    RectangleArea {
        AreaCalculatorComponent:
            PromoteAreaCalculator,
    }
}

Motivation

The main motivation for this is that Rust developers are much more familiar with function-style dependency injection, as compared to dependency injection via getter traits and where clauses. Frameworks like Axum and Bevy has trained a whole generation of Rust developers that they can just write plain functions and get the values from a context automagically.

For CGP to gain adoption, we need to show developers that they can reuse the same function-style dependency injection that they know well, and also use it to get fields from a context. This should significantly lower the barrier, as they don't need to learn the new syntax to start using CGP.

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