-
Notifications
You must be signed in to change notification settings - Fork 205
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
Partial typed dictionnaries #1635
Comments
Related: #783. |
What are the advantages of that over the following? class Product {
final String? name; // nullable because of how I'm interpreting "partial"
final bool? favorite;
const Product({this.name, this.favorite});
// Map<String, dynamic> toJson() => { ... };
}
class ProductAPI {
Future<void> update(String id, Product updates) =>
firebase.collection("products").doc(id).set( updates.toJson() );
} Is it the automatic |
In your code above every property of the product you specified is nullable it does not correctly reflect a Product data, where some fields are not nullable. You could have default values on your database and be sure that when you read a product those fields are never null. However when you update a product, you often want to update a single property. Typescript keyword resolve this. That's the advantage of the utility keywords and how they'd affect a typedDic. Another feature this has is that the properties are string so you can easily read them, which makes it easy to serialize / deserialize. |
It seems you're looking for mutable fields, not final. Instead of creating a new "partial" class Product {
String name;
bool isFavorite;
Product({required this.name, required this.favorite});
}
// in your UI
void toggleFavorite(Product product) => setState(
() => product.isFavorite = !product.isFavorite
);
// if you have other logic
Future<void> updateProperties(Product product) async =>
product.isFavorite = await isFavorite(product); A class Product {
final String name;
final bool isFavorite;
const Product({required this.name, required this.isFavorite});
Product copyWith({
String? name,
bool? isFavorite,
}) => Product(
name: name ?? this.name,
isFavorite: isFavorite ?? this.isFavorite,
);
}
// in your UI
void toggleFavorite(MyData dataModel) =>
dataModel.product = dataModel.product.copyWith(isFavorite: !product.isFavorite); As you can see, If you're looking for a special class that already has |
Absolutely not. This is going a bit on a tangent but I'll try to explain more clearly. I want :
In a real application you would also update the favorite property on the database. You would not just pass the whole product. so you'd have this:
Surely you can see the need of such mechanism for front end applications where updating one property at a time is a thing. Currently there is no way of doing that and it is a inconvenience for me. I'm currently using maps in place I would like the compiler to scream at me when I rename a property. This is really not fun. Now I hope your static meta programing proposal could change that but I'm not sure it could do something like the feature I'm proposing here. Another examples of such shortcoming of the language that could be resolved by this:
Whatever the answer to this is, be it meta programing or something like the feature I propose, it cannot come soon enough as I'm annoyed every day by this. |
I mean, it seems like you are. "Malleable" means changeable, and "updating a single property" means you want to be able to modify its value. I know Dart and Flutter sometimes make mutable data look ancient and obsolete but they're often legitimate solutions. Unless you have a super compelling reason not to (which I'd be glad to advise on), this would work best for your case. As for metaprogramming, that would be relevant here in one of two ways:
|
Then I'm choosing my words poorly and I edited the title. My product is never updated on the front end, my data is not malleable. I'm updating the data on the backend and it's only the representation of the data that is malleable. consider this:
How do you propose I achieve this with a correctly typed update (not using a map) that would allow me to update any combination of properties of a product ? |
So to clarify, you're getting fresh data from a backend but you don't want the app itself to change the data being reflected. So in that case immutable would be a good way to go, but I would suggest that instead of storing the partial changes, you just override it with the complete object, and add a class Product {
final String name;
final bool isFavorite;
Product.fromJson(Map json) :
name = json ["name"],
isFavorite = json ["isFavorite"];
Map<String, dynamic> toJson() => {
"name": name,
"isFavorite": isFavorite,
};
}
class UIData {
// basic data structures for your app
static Map<String, Product> products; // maps names (or IDs) to products
}
class API {
void listen() => firebase.collection("products").snapshots().listen(
(QuerySnapshot snapshot) {
for (final QueryDocumentSnapshot doc in snapshot.docs) {
// get the data represented by this doc and save it to UIData
final Map<String, dynamic> json = doc.data();
final Product product = Product.fromJson(json);
UIData.products [product.name] = product;
}
}
);
/// Updates a single product
Future<void> updateProduct(Product product) =>
firebase.collection("products").doc(product.name).set(product.toJson());
} This is the pattern I use (with a bit more separation between data and firestore) and I find it works well. Hopefully it helps you too. |
We are getting somewhere.
How do you update only the favorite property with this code ? That's my point , I have to use this:
but in typescript I can type that:
|
By updating the whole thing! If you keep the whole object in the database, you don't have to worry about applying specific changes -- just throw away the old object and take the new one. To show that this works, if you try it on Firestore and watch the console, only the property you change will turn orange. So even though you're replacing an entire object, practically speaking you only change what you need. For example: // you say the app doesn't make these changes, so pretend this is happening in the backend
Product a = Product(name: "toy", isFavorite: false);
Product b = Product(name: "toy", isFavorite: true);
await updateProduct(a); // firestore/products/toy = {"name": "toy", "isFavorite": false}
await updateProduct(b); // firestore/products/toy = {"name": "toy", "isFavorite": true} And in your app, the stream listener gets called twice, one with each of the above values. Since |
Consider a Product that is updated by a team of people concurrently and you don't want to override other people changes with old values. You could still have the old version locally because the update didn't get to you and when you update by giving it the whole thing it overrides the change of someone else that made the change 1 second before you. Honestly while I'm not against discussion I think some of the refutations about this are not honest. Updating the whole thing is more akin to a workaround, it's sending more bytes over the network than necessary. I can have a diff mechanism as well, that would work. Ultimately there are solutions, what I'm advocating for is sensible ones, a diff system in place of a partial keyword is not one of them. |
Okay so this part clarified it a lot for me. I played around a lot in DartPad and yeah, I see what you mean. At least in Firebase, you can use Transactions to download the latest data and use some Map logic to merge the two, but you're right that the only way to do it typed is to have a Metaprogramming can help here by automatically creating that class // product.dart
import "partial_classes.dart"; // the PartialClass annotation
part "generated:product.g.dart"; // the partial class itself
@PartialClass(mutable: false)
class Product {
final String name;
final bool isFavorite;
const Product({
required this.name,
required this.isFavorite,
});
} And this gets generated: // product.g.dart
part of "product.dart";
class PartialProduct {
final String? name;
final bool? isFavorite;
const Product({this.name, this.isFavorite});
Product merge(Product other) => Product(
name: name ?? other.name,
isFavorite: isFavorite ?? other.isFavorite,
);
} Then changes would be made to Here would be the metaprogramming code according to #1565, if you're interested// partial_classes.dart
import "dart:code_gen";
/// A macro that generates a new `PartialFoo` from a user-written class `Foo`
class PartialClasses extends ClassMacro {
final bool mutable;
const PartialClasses({this.mutable = true});
/// this code gets passed to `dart format`, don't worry
@override
String generateTopLevel() => [
"class Partial$sourceName {",
// declare nullable (and maybe mutable) fields
for (final Variable field in fields)
"${mutable ? 'final' : ''} ${field.type}? ${field.name}",
// constructor
"const Partial$sourceName({",
for (final String field in fieldNames)
"this.${field.name},",
"});",
// the merge function to merge with the original class
"$sourceName merge($sourceName other) => $sourceName(",
for (final String field in fieldNames)
"$field: $field ?? other.$field,",
");",
"}",
].join("\n");
} The reason I keep touting metaprogramming everywhere is because I see a lot of issues where it's really a matter of boilerplate that no one wants to write. Metaprogramming can help by auto-generating these ideas for you so the Dart team doesn't have to manually implement every special case (of which there are a lot), or can at least take their time. |
The thing about a PartialProduct generated by metaprogramming is that it's just a Product class with every field being nullable and it's not exactly the same as a Partial where the fields just don't exist at all. If you give a PartialProduct to the above update functions you'd have to filter out null values which brings the new issue of: How do you update a field with a null value ? I get that meta-programing would resolve a lot of things just by the nature of it being sort of a "language builder". However I don't think it can solve this, as the data here would be constructed at runtime (You don't know which property the user wants to update in advance). Also something directly integrated in the language gives an higher level of confidence than a third party library that uses meta programming. So things that are truly useful could be still integrated in the language directly. I gotta say that your API looks nice tho |
@cedvdb Did you figure out something eventually or had to resort to using Map? |
I've been using maps, as the alternatives did not look attractive to me, the funny thing is that I saw the notification for your comment while going to this repo for checking the advances in static meta programming while thinking about this issue again because I just add an issue with a typo. So I'm still annoyed by this, weekly 😁. After static metaprogramming I envision still using a map but having static metaprogramming generate the strings for the keys in the map (eg: I'd gladly take the compiler catching those before tests though, simply because it's easier to debug than a failing test. |
Dart should have a data type that allows to work with:
What dart has at the moment is not typed (maps). I propose the use of python TypedDictionnary coupled with utilities a la TypeScript.
use case
Consider this use case: You are on a
Product
page and you want to update certain properties one by one. One way of implementing it in typescript would be:In dart, you'd have to use a map in place of Partial which is not typed.
Typescript utilities
Typescript has a set of utility types that are extremely useful, especially for front end development. In the front end often you are not dealing with a whole entity but a partial one.
Here are some of the utilities (full list)
Partial<Type>
: Constructs a type with all properties of Type set to optional. This utility will return a type that represents all subsets of a given type.Required<Type>
: Constructs a type consisting of all properties of Type set to required. The opposite of Partial.Readonly<Type>
: Constructs a type with all properties of Type set to readonly, meaning the properties of the constructed type cannot be reassigned.Pick<Type, Keys>
: Constructs a type by picking the set of properties Keys (string literal or union of string literals) from Type.and more
typed dictionnaries
While I don't use python, it seems they have something that could solve this, and other issues like json serialization.
Something like that in dart would be fantastic where you'd also allow typescript modifiers. The original use case could be typed:
Static Meta programing:
While meta programing would supposedly solve the issue of data classes I'm not sure it can handle the Partial part.
The text was updated successfully, but these errors were encountered: