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

feat: add a derive macro for create_slice() #1867

Merged
merged 11 commits into from
Oct 24, 2023
Merged

feat: add a derive macro for create_slice() #1867

merged 11 commits into from
Oct 24, 2023

Conversation

SadraMoh
Copy link
Contributor

@SadraMoh SadraMoh commented Oct 8, 2023

Fixes #1635

I still need to work on this (issue with nested structs) but it should illustrate the general idea.

The new #[derive(Lens)] macro is appliable to all global state structs (field structs and tuple structs) and generates "Lens constructor functions" for every field of the struct.

example:

#[derive(Lens, Default)]
pub struct GlobalState {
    count: i32,
    name: String,
}

app.rs

provide_context(create_rw_signal(GlobalState::default()));

and inside the component:

let state = expect_context::<RwSignal<GlobalState>>();
let (count, set_count) = GlobalState::count(state);

@SadraMoh
Copy link
Contributor Author

SadraMoh commented Oct 8, 2023

Improvement ideas

This could even be more concise, if the trait provides provide and expect methods, specific to the struct type. This would allow the macro to generate a wrapper type around RwSignal<GlobalState> , implement the methods in it, which would be referencing self allowing for something like this:

#[derive(Lens, Default)]
pub struct GlobalState {
    count: i32,
    name: String,
}

app.rs

GlobalState::default().provide();

inside the component:

let state = GlobalState::expect();
let (count, set_count) = state.count();

Which is even nicer.

Another idea would be to introduce a lens!(...) macro, that would generate lenses on the fly kind of like what druid does.
This pattern wouldn't be too foreign to solidjs users either.
This would make it much easier to work with nested structs, which is commonly seen with global stores.

A rough concept would look like this:

pub struct OuterState {
    count: i32,
    inner: InnerState,
}

pub struct InnerState {
    inner_count: i32,
    inner_name: String,
}

within the component:

let state = GlobalState::expect();
let (count, set_count) = lens!(state.count);
let (inner_count, set_inner_count = lens!(state.inner.inner_count);

@SadraMoh
Copy link
Contributor Author

A function-like poke! macro was added that effectively provides the lens! api I described above.

I'm honestly more fond of the function-like poke! macro, as it resolves the issue of of nested structs really well, and even provides as you type suggestions because of the way the parser is implemented. I prefer it over the derive macro described in the original feature request.

@gbj
What are your thoughts on this?
Do you have any preference between the two?
Do you think these macros bring value to our users?
Should I continue with this topic or should I scrap it all together?

@gbj
Copy link
Collaborator

gbj commented Oct 13, 2023

@SadraMoh Thanks for your work on this!

I don't understand the name poke! but I like the macro very much. I'd suggest something like slice! as the name, as it corresponds directly to create_slice. My understanding is that

slice!(state.count);

just expands to

create_slice(
    state
    |st: &_| st.count.clone(),
    |st: &mut _, n| st.count = n
)

This makes perfect sense to me and is a nice shorthand.

@jquesada2016 Do you have feedback on the derive macro API here, since it was your idea?

@jquesada2016
Copy link
Contributor

Hey, sorry for the late reply, I've been locked out of my email for a while because my phone broke and had to wait to get it fixed for MFA to work :/

I actually like @gbj's syntax of slice!(state.count). I think we should definitely add this one, which can be a simple declarative macro.

What I was thinking was something like:

#[derive(Slice)[ // or Lens, or Prism, or whatever
struct State {
  count: i32,
  name: String,
  }

which expands to something akin to:

struct State {
  count: i32,
  name: String,
}

#[derive(Clone, Copy)]
struct StateSlice {  // or StateLens, or StatePrism, or whatever...
  count: RwSignal<i32>,
  name: name: RwSignal<String>
}

impl StateSlice {
  pub fn new(state: State) -> Self { /* ... */ }

  pub fn split(self) -> (StateSliceRead, StateSliceWrite) { /* ... */ }
}

#[derive(Clone, Copy)]
struct StateSliceRead {
  count: ReadSignal<i32>,
  name: ReadSignal<String>,
}

#[derive(Clone, Copy)]
struct WriteSliceRead {
  count: WriteSignal<i32>,
  name: WriteSignal<String>,
}

@jquesada2016
Copy link
Contributor

Slice might not be the best name...the idea was to be able to easily bundle up multiple create_signal() calls into a single struct, since this is what most people do anyways. Having a centralized way to eaily create a state struct and easily converting it into a bundle of signals would be really handy.

@SadraMoh SadraMoh marked this pull request as ready for review October 23, 2023 18:04
@SadraMoh
Copy link
Contributor Author

I removed the incomplete derive macro implementation from this branch for now. I'll continue working on it in a separate branch
I also renamed the function-like macro to slice!(...)

@gbj
Copy link
Collaborator

gbj commented Oct 24, 2023

Awesome! Thanks! The CI issues here are unrelated, this all looks good to me.

@gbj gbj merged commit 30370a5 into leptos-rs:main Oct 24, 2023
42 of 58 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a derive macro for create_slice()
3 participants