-
Notifications
You must be signed in to change notification settings - Fork 185
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
possible to copy already-computed memoized fields in build()? #774
Comments
Even better, one could imagine an implementation where this: var val = Value(field: 3);
var val2 = val.rebuild((b) => b..field=3); results in var val = Value(field: 3);
var builder = val.toBuilder();
builder.field = 3;
var val2 = builder.build(); The idea is the same: There is one question of efficiency: in each of the Builder's setters, would we want to pay the cost of doing a full set field(int value) {
if (identical(_$this._field, value) || (_compareOnSet && _$this._field == value)) {
return;
}
_dirty = true;
_$this._field = value;
} Even if This would essentially get all the benefits of Looking at the generated file .g.dart file, my understanding is that currently, the only optimization is that if I create a Builder from a Built object via |
Thanks. It might make sense to have the builder keep the reference to the old value, then compare after building whether the results are the same. But, we have to be careful. For example, it's possible to have fields that are not included in the comparison. With this change, it would no longer be possible to update these fields unless you update some other field at the same time. I'll have a think about this next time I look at implementing a batch of features. |
Ah, I see the problem. Nevertheless, the set field(int value) {
if (identical(_$this._field, value)) {
return;
}
_dirty = true;
_$this._field = value;
} And then use the value of _$Value build() {
_$Value _$result;
try {
_$result = _dirty
? new _$Value._(
field: field,
//...
)
: _$v;
} catch (_) {
String _$failedField;
// ...
} catch (e) {
throw new BuiltValueNestedFieldError(
'Value', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
} In other words, create a new |
Nested builders will be more complicated; those are always new, so |
@davidmorgan For what it's worth, I just encountered another reason not to use the reading of |
Yes, nested builders are great for usability but awkward for correctness :) |
Is there a reason to null out the internal int _field;
int get field =>_$v == null ? _field : _$v.field;
set field(int newField) {
if (_$v == null) {
_field = newField;
} else {
_field = new_field;
_field2 = _$v.field2;
_field3 = _$v.field3;
_fieldNestedBuilder = _$v?._fieldNestedBuilder.toBuilder();
// etc...
_$v = null;
}
} Update: Still, it feels tantalizing close to there being an efficient way to avoid creating new Built objects unless they represent actual new values. This would be great for things like Redux, where you have very frequent updates to a large state tree, most of which leave most of the tree alone in terms of what value it represents, but as currently implemented in built_value, causes brand-new objects to be generated on each update. |
I think the following logic might make sense to allow more eager caching of Built objects, but I'm sure I could be missing something. Each Builder maintains an internal dirty bit The Builder has a public method One way this breaks is that built_value allows non-immutable objects to be contained in I feel like I'm possibly re-inventing the wheel here; I wonder if there is a language that supports true language-level optional immutability, and if so, how it's implemented. There's functional languages like Haskell that force everything to be immutable (no optional mutability), and many languages have something like |
Allowing Other than that though I've taken the approach that it's more useful to allow types we're not sure about, on the understanding that people might supply their own immutable types, and should be aware that things will break if they are mutable :) Anyway, I hope we can improve things here--not sure when I'll get to it, though. |
While profiling my application, I noticed that a supposedly memoized field was being recomputed over and over, even though the object's fields didn't change. However, the object itself changed through a call to
replace
(with identical field values as before).The source of it seems to be that
build
does not copy the memoized fields.I understand the danger here. Suppose we have
If
build
naively copied the fieldderived
, then it would still be the value 6 at the last print statement, even though we want it to be recomputed to be 60 since the non-derivedfield
has changed.Nonetheless, it would be great if the
ValueBuilder
class could detect whether any of its fields had changed since it was created, and if not, then any derived fields that have already been computed could be copied inbuild
to avoid needing to re-compute them.One way to do this would be to have
build
copy all of the derived fields by default, but for the writable field setters, on the first write to any of them, to set all the underlying derived fields tonull
. (in the example above, this would be the field named__derived
in the generated .g.dart file). Or to be a bit more efficient, add to eachBuilder
class a single_dirty
field that's initiallyfalse
, and is set totrue
when any setter is called, andbuild()
only copies memoized fields into the newBuilt
object if_dirty == false
.The text was updated successfully, but these errors were encountered: