-
-
Notifications
You must be signed in to change notification settings - Fork 20
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
bon
3.0 wishlist (far, maybe never accepted breaking changes)
#85
Comments
bon
3.0 wishlist (far future)bon
3.0 wishlist (far maybe unreachable future)
bon
3.0 wishlist (far maybe unreachable future)bon
3.0 wishlist (far, maybe unreachable future)
bon
3.0 wishlist (far, maybe unreachable future)bon
3.0 wishlist (far, maybe never accepted major breaking changes)
bon
3.0 wishlist (far, maybe never accepted major breaking changes)bon
3.0 wishlist (far, maybe never accepted breaking changes)
Allow for groups and conflicts like in clap. Also saw something like that in compile time builder, cannot find it now, as I recall it needed some solver to be run at build time. These features allow to state:
I have my manually crafted builders and do:
|
Hi @dzmitry-lahoda, this issue is specifically about breaking changes. What you proposed is not a breaking change, therefore I've extracted it into a separate issue #110. |
The function builder required use bon::builder;
#[builder]
fn greet(name: &str, level: Option<u32>) -> String {
let level = level.unwrap_or(0);
format!("Hello {name}! Your level is {level}")
}
// required fields into positional arguments
let greeting = greet("Bon");
assert_eq!(greeting, "Hello Bon! Your level is 0");
// use <fn>_with for positional arguments and closure to set provide optional fields
let greeting = greet_with("Bon", |b| { b.level(24); });
assert_eq!(greeting, "Hello Bon! Your level is 24");
struct Tensor {}
#[bon]
impl Tensor {
pub fn new() -> Self {
Tensor{}
}
#[builder]
fn op1(&self, x: &str, level: Option<&str>) -> Tensor {
Tensor{}
}
#[builder]
fn op2(&self, y: &str, level: Option<&str>) -> Tensor {
Tensor{}
}
}
let tensor = Tensor::new();
tensor.op1("x").op2("y");
tensor.op1_with("x", |b| b.level(2)).op2("y"); verse existing let tensor = Tensor::new();
tensor.op1("x").call().op2("y").call();
tensor.op1("x").level(2).call().op2("y").call(); |
Hey @cksac the requirement of the See an alternative feature that may solve/alleviate this problem. I have a draft PR for it #125 which you can test for yourself (the release target is somewhere by the end of this week). It allows you to pass arguments into the start_fn and finish_fn. But note that it's not fully finished yet, and the syntax may change a bit before merge. I can't see a practical application in the example with the use bon::builder;
#[builder]
fn greet(#[builder(pos = start_fn)] name: &str, level: Option<u32>) -> String {
let level = level.unwrap_or(0);
format!("Hello {name}! Your level is {level}")
}
let greeting = greet("Bon").call();
let greeting = greet("Bon").level(24).call();
#[derive(bon::Builder)]
#[builder(start_fn = x, finish_fn = y)]
struct Point {
#[builder(pos = start_fn)]
x: u32,
#[builder(pos = finish_fn)]
y: u32,
}
let _ = Point::x(1).y(2); @cksac let me know if this solves (or at least helps with) your problem |
One more trick. If you want to avoid calling the finishing method, just make the finishing method accept one of the required fields (which, additionally forces the caller to pass the value for this member as the last one in the chain). Annotate all optional fields as In this example, after the use bon::Builder;
#[derive(Builder)]
#[builder(finish_fn = arg2)]
struct Example {
arg1: i32,
#[builder(pos = finish_fn)]
arg2: i32,
#[builder(skip)]
optional_1: Option<i32>,
#[builder(skip)]
optional_2: Option<i32>,
}
impl Example {
fn optional_1(mut self, value: i32) -> Self {
self.optional_1 = Some(value);
self
}
fn optional_2(mut self, value: i32) -> Self {
self.optional_2 = Some(value);
self
}
}
let _ = Example::builder().arg1(1).arg2(2).optional_1(99); I'll probably need to document all these patterns in the "Patterns" section of the docs. Note that al of the above requires #125 to be released. |
I think we can generate two function variants from original function which use the generate builder? like use bon::builder;
#[builder]
fn greet(name: &str, level: Option<u32>) -> String {
let level = level.unwrap_or(0);
format!("Hello {name}! Your level is {level}")
}
// generated helper functions
fn _greet(name: &str) -> String {
greet().name(name).call()
}
fn _greet_with<F>(name: &str, set: F) -> String
where F: FnOnce(GreetBuilder) -> GreetBuilder
{
let builder = greet().name(name);
set(builder).call()
} |
with custom finish_fn, it seems even harder to read? tensor
.op1("x") // TensorOp1Builder
.level(24) // Tensor, finish_fn of op1
.op2("y") // TensorOp2Builder
.call() // Tensor, finish_fn of op2 compare to tensor
.op1("x") // Tensor
.op2_with("y", |b| b.level(2)) // Tensor |
As for me, the closure syntax is harder to read and compose because now your code is nested inside of anther function. You can no longer easily use the partial builder pattern and you can't do an early return from the function. See the comment idanarye/rust-typed-builder#66 (comment) from the author of Could elaborate on your real use case where you'd like the syntax proposed here? I'd like to understand the logic behind the builder in your case to better understand the problem domain. |
In the example shown here the existing API already looks nice. Every method returns a Basically, as I understand your |
The example I show did't apply bon yet, and some ops will take more than 1 args. |
If you'd mix a builder in the middle of these combinators layering (especially, if you remove the explicit tensor
.operation_1(some_arg, some_arg2) // -> Builder
.optional_param_to_operation_1("foo") // -> Builder
.call() // -> Tensor (now that we've set all optional params, call the next combinator) ...
.operation_2(some_arg, some_other_arg) // -> Builder The problem that the caller needs to set optional parameters and identify the "end" when no more optional parameters should be expected requires an explicit The closure syntax looks unpretty, and I would like to avoid generating multiple variants of the same API and setters if possible to keep compile times lower and cognitive overhead lower as well (when there is just one way to do something, it makes your code structure more consistent) |
Btw, in the example initial here (I modified it a bit): impl Tensor {
#[builder]
fn op1(&self, x: &str, level: Option<&str>, op2: Option<&'static str>) -> Tensor {
Tensor{} // ^^^ added a new parameter
}
#[builder]
fn op2(&self, y: &str, level: Option<&str>) -> Tensor {
Tensor{}
}
}
let tensor = Tensor::new();
tensor.op1("x").op2("y");
tensor.op1_with("x", |b| b.level(2)).op2("y"); The Same thing with tensor.op1_with("x", |b| b.level(2)).op2("y"); Is that |
here is a working example showing what I want to achieve. And comparison of two style. use bon::bon;
struct Tensor {}
#[bon]
impl Tensor {
pub fn new() -> Self {
Tensor {}
}
#[builder]
fn _sum(
&self,
dims: &[i64],
#[builder(default)] keep_dim: bool,
#[builder(default)] mean: bool,
) -> Tensor {
Tensor {}
}
// GENERATED, all required parameters are positional
fn sum(&self, dims: &[i64]) -> Tensor {
self._sum().dims(dims).call()
}
// GENERATED, all required parameters are positional, with clouser to set optional parameters
fn sum_with<F, __KeepDim, __Mean>(&self, dims: &[i64], set: F) -> Tensor
where
for<'__f1, '__f2> F: FnOnce(
TensorSumBuilder<
'__f1,
'__f2,
(
::bon::private::Set<&'__f1 [i64]>,
::bon::private::Unset<::bon::private::Optional>,
::bon::private::Unset<::bon::private::Optional>,
),
>,
) -> TensorSumBuilder<
'__f1,
'__f2,
(::bon::private::Set<&'__f1 [i64]>, __KeepDim, __Mean),
>,
__KeepDim: bon::private::IntoSet<Option<bool>, TensorSumBuilder__keep_dim>,
__Mean: ::bon::private::IntoSet<Option<bool>, TensorSumBuilder__mean>,
{
let b = self._sum().dims(dims);
set(b).call()
}
#[builder]
fn _mean(&self, dims: &[i64], #[builder(default)] keep_dim: bool) -> Tensor {
Tensor {}
}
fn mean(&self, dims: &[i64]) -> Tensor {
self._mean().dims(dims).call()
}
fn mean_with<F, __KeepDim>(&self, dims: &[i64], set: F) -> Tensor
where
for<'__f1, '__f2> F: FnOnce(
TensorMeanBuilder<
'__f1,
'__f2,
(
::bon::private::Set<&'__f1 [i64]>,
::bon::private::Unset<::bon::private::Optional>,
),
>,
) -> TensorMeanBuilder<
'__f1,
'__f2,
(::bon::private::Set<&'__f1 [i64]>, __KeepDim),
>,
__KeepDim: ::bon::private::IntoSet<Option<bool>, TensorMeanBuilder__keep_dim>,
{
let b = self._mean().dims(dims);
set(b).call()
}
}
fn main() {
let t = Tensor::new();
// exiting
let output = t
._sum()
.dims(&[1, 2])
.keep_dim(true)
.mean(false)
.call()
._mean()
.dims(&[1])
.keep_dim(true)
.call()
._sum()
.dims(&[1])
.call()
._mean()
.dims(&[1])
.call();
// proposed
let output = t
.sum_with(&[1, 2], |b| b.keep_dim(true).mean(false))
.mean_with(&[2], |b| b.keep_dim(true))
.sum(&[1])
.mean(&[1]);
} |
@cksac how about this? What if struct Tensor {}
// Define optional parameters in a struct
#[derive(bon::Builder)]
struct SumOptionalParams {
#[builder(default)]
keep_dim: bool,
#[builder(default)]
mean: bool,
}
// This impl will be generated by `bon::Builder` (maybe by default, or maybe opt-in via an attribute config)
impl<KeepDim, Mean> From<SumOptionalParamsBuilder<(KeepDim, Mean)>> for SumOptionalParams
where
KeepDim: bon::private::IntoSet<Option<bool>, SumOptionalParamsBuilder__keep_dim>,
Mean: bon::private::IntoSet<Option<bool>, SumOptionalParamsBuilder__mean>,
{
fn from(builder: SumOptionalParamsBuilder<(KeepDim, Mean)>) -> Self {
builder.build()
}
}
impl Tensor {
pub fn new() -> Self {
Tensor {}
}
fn sum<T: Into<SumOptionalParams>>(
&self,
dims: &[i64],
optional: impl FnOnce(SumOptionalParamsBuilder) -> T,
) -> Tensor {
let params: SumOptionalParams = optional(SumOptionalParams::builder()).into();
Tensor {}
}
}
let t = Tensor::new()
.sum(&[1, 2], |b| b.keep_dim(true).mean(false)); This code compiles with the current version of Or maybe it's better to have a trait that could be used as a geneiric bound for the return type of the closure smth like // or TotalBuilder, IDK
trait CompleteBuilder {
type Output;
fn build() -> Self::Output;
} but I'm not sure about it yet. This will conflict with builders that specify additional parmeters in |
@Veetaha , with when generated this style, |
Yeah, it indeed requires more code to write, but I doubt there is a popular demand for such builder syntax, although I'm ready to be proven wrong. The gist is that if there is a |
I don't think there is anything |
Closing this in favour of #156 |
Here are some items that we may consider doing as part of the
bon
3.0 major release. These are specifically breaking changes that we can't afford to do as part of a minor/patch release.Builder
type private (unexposed) by default. E.g. the builder type should contain__
as a prefix and be#[doc(hidden)]
to effectively make it an "anonymous type". The reason is to hide it from therustdoc
output by default because its type signature is rather complex and noisy, such that it's unlikely people may read its docs.Verdict
skip/default
expressions, and today's order is completely consistent with that.The text was updated successfully, but these errors were encountered: