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

implemented the builder api for salsa inputs #418

Closed
wants to merge 2 commits into from

Conversation

OLUWAMUYIWA
Copy link
Member

Ref: #385

@netlify
Copy link

netlify bot commented Sep 29, 2022

Deploy Preview for salsa-rs canceled.

Name Link
🔨 Latest commit 72affc7
🔍 Latest deploy log https://app.netlify.com/sites/salsa-rs/deploys/6335cb726f47f000085938fd

let input_index = self.input_index();
let field_indices = self.all_field_indices();

let buidler: syn::ItemStruct = parse_quote! {
Copy link
Member

Choose a reason for hiding this comment

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

Typo

Suggested change
let buidler: syn::ItemStruct = parse_quote! {
let builder: syn::ItemStruct = parse_quote! {

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. Will fix

}
};

(buidler, impls)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
(buidler, impls)
(builder, impls)

let build_fn: syn::ImplItemMethod = if self.0.is_isingleton() {
parse_quote! {
fn build(&mut self, __db: &#db_dyn_ty) -> std::result::Result<#ident, std::boxed::Box<dyn std::error::Error>> {
let durability = self.durability.ok_or_else(|| "durability not provided")?;
Copy link
Member

Choose a reason for hiding this comment

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

Why panic when a durability isn't specified, instead of using the default of low? I think one of the main advantages of a builder API is that you can have a lot of options, most of which aren't required, and you can only make the changes you need over sensible defaults.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmn true. Thanks. Will fix

let __id = __ingredients.#input_index.new_singleton_input(__runtime);

#(
__ingredients.#field_indices.store_new(__runtime, __id, self.#field_names.clone().ok_or_else(||"field not provided")?, durability);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think using the same durability for all the fields gives enough flexibility to the user. Wouldn't it be better to have a method with_durabilities which works like with_fields? The current with_durability method could stay, but I'm not sure if the name isn't slightly ambiguous, because we have multiple fields all with their own durability.

Copy link
Member Author

@OLUWAMUYIWA OLUWAMUYIWA Oct 3, 2022

Choose a reason for hiding this comment

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

Thought about having with_durabilities too, but I dropped the idea because it sorts of requires the user to keep the order of fields in mind. I don't know if that cognitive overhead is okay. @MihailMihov, @nikomatsakis , @xffxff
Though personally, I think it's better.

Another option I'm considering is having with_field, and not with_fields, so we get to set the fields one by one and take a tuple, i.e. its value and it's durability as arguments. That localizes concerns. But, is taking tuples neat? Also, is setting the fields one by one okay?

Thanks Mihail!

Copy link
Member

Choose a reason for hiding this comment

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

Thought about having with_durabilities too, but I dropped the idea because it sorts of requires the user to keep the order of fields in mind. I don't know if that cognitive overhead is okay.

The cognitive overhead point is something that I didn't consider, but I did go over the idea with the tuples and decided to not suggest it, because it wouldn't look very good if we needed to add more options for the fields in the future. And then we also lose the ability to change just 1 or 2 things about a field, we'd need to set all 5 (again this only applies if we do consider having more than just a value and a durability).

Also if tuples were an option all the intermediate structs produced by the builder would have an incomplete state, and the user would need to keep track of all the fields being set.

But that's just my opinion I think we should hear what others have to say.

self
}

fn with_fields(&mut self, #(#field_names: #field_tys,)*) -> &mut Self {
Copy link
Member

Choose a reason for hiding this comment

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

with_values would be a better name in my opinion, but the current is fine too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmn. with_fields makes it obvious to the user what they're about to set. Wouldn't with_values be a little ambiguous? I really don't know.

Copy link
Member

Choose a reason for hiding this comment

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

I think of a field as something which has a value, a durability and maybe other things associated with it, which is why with_values is better in my opinion as it communicates that you're setting just the value of the field. If I saw with_fields my first thought would be that it takes a struct or tuple which contains everything about a field. Maybe someone else could comment on what is the better option, but I think both are good?

.with_durability(Durability::HIGH)
.with_fields(20, 40)
.build(&db)
.unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

Returning a Result from build isn't useless, but I also feel like almost always having to unwrap it will be something very repetitive and rarely useful. If you agree with my suggestion that the durability should have a default I have a different idea, which I think will make the API better, instead of having the build method be the terminal one, we could instead move that over to with_fields and we'd no longer need to return a Result, because we know that we have the values and the other thing we need is the db, but there a panic would be fine.

let input_from_builder = MyInput::new_builder()
        .with_db(&db)
        .with_durability(Durability::HIGH)
        .with_fields(20, 40);

I think this makes the API calls shorter, with no downsides and it would be closer to the salsa::Setter API. The only thing that I kind of don't like is how to fields method is named similarly to the other ones, which doesn't clearly show that it's the terminal method that actually builds the type.

Copy link
Member Author

Choose a reason for hiding this comment

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

The only thing that I kind of don't like is how to fields method is named similarly to the other ones, which doesn't clearly show that it's the terminal method that actually builds the type.

That's a real concern. I actually think builders should have build, and should naturally return Results except when the value we're building can comfortably have default values all through. In this case, fields cannot have default values. And, panicking inside with_fields? hmmn maybe people don't expect that. But I wouldn't know. I don't have much experience here. If you're so sure...

Maybe others have something to say?
@xffxff @nikomatsakis @crlf0710

Copy link
Member

Choose a reason for hiding this comment

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

In this case, fields cannot have default values.

That and the database are the two things that we need to set for the struct to be complete. I think either one of those could be used as the build method and panic only if the other is missing, but it's a tradeoff where we make the API calls slightly shorter, but lose a bit of clarity of which method is the one that builds the type.
But panicking instead of returning Result and making either with_db or with_fields the build method are two separate things. If you're sure that the build should return Result you can still consider putting that logic in with_db or with_fields.

But I wouldn't know. I don't have much experience here. If you're so sure...

I don't either, so take what I say with a grain of salt. I just wanted to bring it up as a talking point, so we could discuss it with the others.

Copy link
Member

Choose a reason for hiding this comment

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

Coming back to this with fresh eyes, a few thoughts--

  1. I think that we should strive to avoid Result that are statically known to not apply.
  2. I don't love having with_values take a tuple, I think I'd rather have a method per field.
  3. This seems to imply we may need to do "type tricks" to make a builder that is statically known to have a value for all fields etc.

It seems like we should back up from the PR and first we be more specific about the API that we want for an example.

@nikomatsakis
Copy link
Member

@OLUWAMUYIWA I know I disappeared a long time here! I'm still interested in this PR, are you still interested to pursue it? (Totally understand if not)

@OLUWAMUYIWA
Copy link
Member Author

Hi @nikomatsakis I want to finish it up! I'll get on it.

Copy link

netlify bot commented Apr 3, 2024

Deploy Preview for salsa-rs canceled.

Name Link
🔨 Latest commit 2895e6a
🔍 Latest deploy log https://app.netlify.com/sites/salsa-rs/deploys/660ca5f89fae150009a975b0

@nikomatsakis
Copy link
Member

@OLUWAMUYIWA oh, awesome!

@nikomatsakis
Copy link
Member

@OLUWAMUYIWA

What I've found really useful in the past is to write manually the code I expect to generate. Maybe you can make a gist with an example input and output and we can discuss that? Else I can do it for you if you prefer :)

@nikomatsakis
Copy link
Member

@OLUWAMUYIWA I'm still interested here, but note that we did some big renamings and things, so you'll definitely have to rebase etc.

Given its age, I'm going to go ahead and close this PR-- please reach out on Zulip or comment in the GIthub issue though if you want to discuss next steps.

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.

3 participants