-
Notifications
You must be signed in to change notification settings - Fork 14
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
Consider adding API for changing deeply nested values #51
Comments
In the case of Java Protobuf, they've built and are showcasing a special feature of the builder API to reduce the amount of code needed to modify deeply nested fields. We don't have anything purpose-built, but the DSL provides a reasonably clean way to do this sort of thing out of the box. Using their example: message Foo {
int32 val = 1;
// some other fields.
}
message Bar {
Foo foo = 1;
// some other fields.
}
message Baz {
Bar bar = 1;
// some other fields.
} We can modify deeply nested fields: baz = baz.copy {
bar = bar!!.copy { // `bar` references the DSL's value of `baz.bar` which is set when invoking `copy()`
foo = foo!!.copy { // same here for `foo` and `baz.bar.foo`
`val` = 10
}
}
} It might be counterintuitive that you can reference |
Is there any way for the DSL to allow this at some point? baz = baz.copy { bar.foo.`val` = 10 } |
When you're in the context of If we wanted to copy the Java library, it might look like exposing baz = baz.copy { bar!!.foo { `val` = 10 } } |
So I'm correct in understanding that a chained call will not be possible: baz = baz { bar.foo.`val` = 10} From a syntactic sugar point of view it would be much preferable to be able to pass a lambda to a (nullable?) instance and have it implicitly be a call to baz = baz { bar = bar { foo = foo { `val` = 10 } } } |
I did some experimentation and it is possible to define a method that behaves the way you propose: class Bar(...) : KtMessage {
class Deserializer : KtDeserializer<...> {
operator fun Bar?.invoke(dsl: BarDsl.() -> Unit) =
Bar {
if (this@invoke != null) {
foo = this@invoke.foo
unknown = this@invoke.unknown
}
dsl()
}
}
}
} I have a few concerns with this approach though: we chose Also, a nullable receiver (by design) sweeps the inherent protobuf nullability of the property under the rug. Part of the advantage of using protokt over protobuf-java is that the optional nature of fields with message types is exposed as nullability of their associated Kotlin properties, which forces users to carefully consider the possible states of a deserialized object. I could see a situation where you commit a capital-letter typo leading to inadvertently copying an instance instead of creating a new one: In our nullable-receiver world, consider a situation where baz = baz {
bar = bar {
foo = foo {
`val` = 10
}
}
} baz = baz {
bar = Bar {
foo = foo {
`val` = 10
}
}
} Suppose you always want to create a new instance. One day if you get an instance of If the receiver was not nullable, you'd be forced to use This is of course getting away from the original issue about modifying deeply nested fields more concisely. Google's |
Thank you for your detailed explanation. I'm still quite new to protobuf in general so I appreciate you explaining the reasons behind certain decisions. I'd love a marriage of what philosophy you're currently working with and the way Java uses their builder, but I understand that it's either/or, or you risk bloat since there's no big reason to do so. |
A small correction to my post above: it looks like protobuf-java doesn't eagerly automatically convert nested messages to builders, but instead allows programmatic setting of messages as if they were builders, doing conversion on the fly and invalidating previously set versions of the nested message as a message. Short of alternative naming conventions I think the unification would be to expose the lowercase builder. I'll have to keep thinking about it and bring it up internally if there's a strong desire to support that pattern. Do you mind if I close this issue for now? |
I do hope you will end up finding a way to support a builder-like pattern that enables easy manipulation of (deeply) nested messages. It is common for me to use a message inside a list inside a message inside yet another message. Currently a lot of what feels like boilerplate is involved. I look forward to hearing more, but feel free to close this for now. |
I'll leave it open with a name better reflecting the eventual goal. |
The documentation currently doesn't provide an explanation for how to change deeply nested values. The official documentation on generated Java code provides an explanation of exactly this case.
The text was updated successfully, but these errors were encountered: