Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EventAdapter implementation (#1) #4

Open
wants to merge 118 commits into
base: main
Choose a base branch
from
Open

EventAdapter implementation (#1) #4

wants to merge 118 commits into from

Conversation

ilslv
Copy link
Member

@ilslv ilslv commented Aug 31, 2021

Part of #1

Synopsis

For now there is no way to deal with schema evolution.

Solution

Introduce es::Adapter, which allows to transform incoming Stream of events into Stream of transformed Events by defining Strategy on every VersionedEvent leaf.

Checklist

  • Created PR:
    • In draft mode
    • Name contains Draft: prefix
    • Name contains issue reference
    • Has k:: labels applied
    • Has assignee
  • Documentation is updated (if required)
  • Tests are updated (if required)
  • Changes conform code style
  • CHANGELOG entry is added (if required)
  • FCM (final commit message) is posted
    • and approved
  • Review is completed and changes are approved
  • Before merge:
    • Milestone is set
    • PR's name and description are correct and up-to-date
    • Draft: prefix is removed
    • All temporary labels are removed

ilslv and others added 30 commits August 13, 2021 14:34
@ilslv ilslv requested a review from tyranron October 14, 2021 09:23
Copy link
Member

@tyranron tyranron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilslv we're close to a relatively perfect implementation, but to be the one, still more bikesheddings are required. Especially, in terms of context stuff.

@@ -134,6 +135,7 @@ jobs:
- arcana-codegen-shim
- arcana-codegen
- arcana
- arcana-chat-example
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that there will be more examples in future, it's better to have prefix arcana-example for all of them, so arcana-example-chat would be better here.

}

#[derive(Clone, Copy, Debug)]
pub struct Adapter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having

impl adapter::Returning for Adapter {
    type Error = Infallible;
    type Transformed = event::Chat;
}

which is quite confusing, let's desolve this to something like this:

#[derive(Clone, Copy, Debug, EventAdapter)] // let's also make alias `event::Adapter`
#[adapter(into = event::Chat)]
#[adapter(err = Infallible)] // optional, `Infallible` should be default
pub struct Adapter;

}
}

type IntoFn<FromEvent, IntoEvent> = fn(FromEvent) -> IntoEvent;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this type alias the bound is actually shorter and clearer.


impl<T: ?Sized> AnyContext for T {}

impl Borrow<(dyn AnyContext + 'static)> for () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is quite akward. As far as I see, we use this dyn AnyContext only in places where we don't really require any real context. We pay for dynamic dispatch out of nothing.

What stops us from using () or some custom EmptyContext?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many issues here goes from coherence problem, thus having impl<T: ?Sized> Borrow<T> for T in std really gives us headache here.

BUT!!!1

We do really have here the situation where our code is between user's ones, so we do have control on both ends. This allows us to introduce custom wrapper types to overcome coherence rules in a way we need, use those wrappers in our inner machinery and trait bounds, and unwrap on user's ends, so library users don't ever touch them! (We do similar trick with our event::Initial wrapper)

Let's see...

#[derive(Clone, Copy, Debug, RefCast)]
#[repr(transparent)]
pub struct Context<T: ?Sized>(T);

#[derive(Clone, Copy, Debug, RefCast)]
#[repr(transparent)]
pub struct InnerContext<T: ?Sized>(T);

impl<T: ?Sized> Borrow<()> for Context<T> {
    fn borrow(&self) -> &() {
        &()
    }
}

impl<X: ?Sized, Y: ?Sized + Borrow<X>> Borrow<InnerContext<X>> for Context<Y> {
    fn borrow(&self) -> &Inner<X> {
        RefCast::ref_cast(self.0.borrow())
    }
}

Now we have impls working in the way we need, and they don't impose any coherence problems.

The last part we need is to chage bounds like:

Ctx: Borrow<Ctx2>

to

Context<Ctx>: Borrow<Ctx2>

on one side, and correct our Strategys impls to contain either type Context = () or type Context = InnerContex<Inner::Context>.

And, also, wrapping/unwrapping on both ends.

Let's give this idea a shot!

Copy link
Member Author

@ilslv ilslv Oct 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyranron Context<Ctx>: Borrow<Ctx2> isn't really possible because of compiler error: `?Trait` bounds are only permitted at the point where a type parameter is declared. I'll try to work around it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyranron

I've managed to work around this by wrapping Ctx inside Context<Ctx> in Adapter blanket impl and later working with it like Ctx. Default Strategies work just fine with it.

But there is another problem: Custom Strategy. So there's a tradeoff:

  1. Require users to wrap dyn Trait in InnerContext and leave it as is in case of ().
  2. Lift that requirement and leave them dyn AnyContext-like approach.

This tradeoff arises, because for option 1 we need to provide additional support on our side, that will break option 2.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilslv

Context<Ctx>: Borrow<Ctx2> isn't really possible because of compiler error: `?Trait` bounds are only permitted at the point where a type parameter is declared.

Yeah, that's why I was (and am) insisting on using associative types. So we have the concrete type to check there from the downstream traits.

But there is another problem: Custom Strategy. So there's a tradeoff:

  1. Require users to wrap dyn Trait in InnerContext and leave it as is in case of ().
  2. Lift that requirement and leave them dyn AnyContext-like approach.

This tradeoff arises, because for option 1 we need to provide additional support on our side, that will break option 2.

As I've said before, it shouldn't be the case, because we can omit making users writing InnerContext in their signatures, by just using them in bounds.

Let's discuss it in a talk.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilslv so OK, let's just use in customize either type Context = (); or type Context = Borrowed<Something>. Where the Borrowed is just renamed InnerContext.

Also, move Borrowed to a top-level spell module, so it can be imported as use arcana::spell::Borrowed;.

I've though quite a while about this and haven't come up with a better solution. We always may adjust it in future once the one appears.

/// [`Event`]: crate::es::Event
#[derive(Debug, RefCast)]
#[repr(transparent)]
pub struct Wrapper<A>(pub A);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this should be named somehow more meaningful.

# Conflicts:
#	Cargo.toml
#	Makefile
#	codegen/Cargo.toml
#	codegen/impl/Cargo.toml
#	codegen/impl/src/es/event/mod.rs
#	codegen/shim/Cargo.toml
#	core/Cargo.toml
#	core/src/es/event/mod.rs
#	core/src/lib.rs
#	src/lib.rs
@ilslv
Copy link
Member Author

ilslv commented Nov 19, 2021

@tyranron new nightly release kinda broke our implementation, but I've managed to fix it.

What changed

Discussion lead to rfc: 1 and 2.

TLDR: GATs now are requiring Self: 'gat_lifetime. This isn't set in stone and is subject to change.

What broke out implementation

Adding Self: 'out to all TransformedStreams should have fix the problem, but it looks like implementation of this feature is broken for now.

Basically in addition to Self: 'out compiler requires every parameter in trait to be : 'out. This causes problems to Transformer trait and its 'ctx lifetime parameter: MRE.

Workaround for now is to split Transformer into TransformerTypes which consists only of associated types and Transformer: TransformerTypes which has only fn transform() method inside.

UPD: Issue has been resolved, so Transformer and TransformerTypes are united again.

@ilslv ilslv requested a review from tyranron November 22, 2021 06:14
@ilslv ilslv removed the request for review from tyranron December 14, 2021 14:05
This reverts commit da2c4ec.
This reverts commit 28b209a.
This reverts commit 1654613.
This reverts commit 40bd6d7.
# Conflicts:
#	core/src/lib.rs
@tyranron tyranron changed the base branch from master to main July 1, 2022 10:14
impl<Ev: ?Sized, Data> Raw<Ev, Data> {
#[doc(hidden)]
#[must_use]
pub const fn __arcana_events() -> [(&'static str, &'static str, u16); 0]
Copy link
Contributor

@50U10FCA7 50U10FCA7 Dec 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilslv Maybe it's better to define a trait for this?

For now its impossible to generalize over the events of Event/VersionedEvent, but such thing will be useful for loading events from some storage/repo for example.

playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c3c3ccffdb866045cfc9ba4a286b37d2

fork showcase: https://github.com/50U10FCA7/arcane/tree/1-EventAdapter-fork

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@50U10FCA7

Maybe it's better to define a trait for this?

Of course it should be hidden under a trait, just at the time there were some fundamental limitations, that wouldn't allow it.

@tyranron tyranron mentioned this pull request Jan 17, 2023
15 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request k::api Related to API (application interface) k::design Related to overall design and/or architecture
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants