-
-
Notifications
You must be signed in to change notification settings - Fork 57
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
Improve bindings #127
base: main
Are you sure you want to change the base?
Improve bindings #127
Conversation
This allows for chaining `.as` calls and unwraps nested bindings
This allows for maximum backwards compatibility without lying about inheritance.
I've done some experimenting and the best I can do for a prop<K extends keyof Value>(key: K): Value extends Connectable ? TransformBinding<Value, DataBinding<Value[K]>> : never {
return this.as((obj: any) => {
if (typeof obj.connect !== "function") {
throw new Error("Binding.prop only works on bindings containing Connectables.")
}
return bind(obj, key) as DataBinding<Value[K]>
}) as any;
} This function would return EDIT (like 5 minutes later lol): This works for some reason and I have no idea why! // look ma, no type assertions!
prop<T extends Connectable, K extends keyof T>(this: Binding<T>, key: K): Binding<T[K]> {
return this.as((obj) => {
if (typeof obj.connect !== "function") {
throw new Error("Binding.prop only works on bindings containing Connectables.")
}
return bind(obj, key)
});
} Since it only uses It errors properly, too! const a = Variable(123);
// dummy connectable because I was too lazy to make a proper one
const b = Variable({
data: "abc",
connect(signal, callback) {
return 0;
},
disconnect(id) {
},
} as Connectable);
bind(a).prop("toString") // error, yay!
bind(b).prop("abc") // the key has intellisense and the result is inferred as `Binding<string>` :D I will probably implement this |
The implementation was the same anyway
I consider the new bindings to basically be feature-complete by now, so I'm marking this as ready for review. It is still untested in a real app, though, and the docs will need to change a bit (which I want to do at the end, since stuff may still change). |
@Mabi19 you might find this interesting #132 (comment) |
Oh yeah, co-opting RxJS or similar is definitely an option to solve these issues too - though my implementation was born mostly from wanting to hack on the Astal bindings for fun. Besides, having a "native" implementation of deep reactivity is nicer than making wrappers on observables IMO |
It's great to have options :) |
I also cleaned up a spelling mistake and corrected a GTK import
With the docs updated, I consider the implementation done. The only thing that could be implemented now is However, I did recently have an idea to implement promise unwrapping that would be way simpler. That is, instead of trying to adapt The main issue with promises is that you can't I think that tracking promise values externally - for example with a |
Initial testing is done, and it seems to work fine after I fixed one minor oopsie along the way. However, for some reason |
It appears that this is due to the internal callback of I'm not sure whether making it EDIT: I've determined that changing the type of the transform function should be safe, so I've done it. |
This also makes it so that inner reactivity is only tracked when something's subscribed
I found and fixed a potential issue where inner subscriptions wouldn't get cleaned up. I don't think this PR can be any more done at this point (but I've thought that like thrice already, so I'll probably prove myself wrong again :P) |
A month and a half later: I don't think it makes much sense for the Also, I think that having a helper that takes a |
I've been thinking a little about how to prevent common mistakes with bindings (mostly coercing them to values directly). Well, extra docs on how they work and what you shouldn't do would be one step towards that, as well as adding passive-aggressive doc comments to |
I was playing around with the binding implementation and made something useful.
This PR allows for two new things:
.as
calls properly, i.e.bind(num).as((v) => v * 2).as((v) => v * 2)
will multiply by 4Binding
s obtained from.as()
callbacks, i.e.bind(primaryNetworkKey).as((key) => bind(network, key)).as((net) => bind(net, "icon_name"))
will return a string wrapped in a single binding while preserving reactivity.However, I
m notoriginally wasn't sure on a lot of the implementation (which is why thisiswas a draft PR). Namely:This means that you can't just check that something is a binding withinstanceof Binding
anymore, so it breaks backwards compatibility.BindingTransform
andDataBinding
currently aren't type compatible. This breaks backwards compatibility even more.BindingTransform
currently isn't exported out of the module. This makes the first issue even worse.This may be remedied by (a) exportingBindingTransform
, (b) using a little-known technique called lying by adding aSymbol.hasInstance
handler toBinding
that also returns true if it's aBindingTransform
, or (c) all of the above.Even more extreme measures would be needed to makeBoth implementations now extend aBindingTransform
type-compatible withBinding
. I'm currently thinking makingBinding
an interface (so that the one returned bybind()
's and the one returned by.as()
's private properties don't need to match)Binding<T>
abstract base class, so you don't need to care which one you have (as long as you have a concreteT
- only one of the subclasses unwraps nested bindings).Deep reactivity is still ugly, and a shorthand for the common case of a constant property key would be really nice. I thought of adding aA shorthand has been added.prop(key: string)
handler to bindings which returnsthis.as((obj) => bind(obj, key))
, but that can't be typed easily as not all bindings will outputConnectable
s.Promise
s here because it would require an additional layer of indirection, and I don't see the use case for async bindings myselfI haven't been able to test this in an actual app, mostly because of point 1 (which is now invalid). I'll probably try it later.Both simple test cases and my actual setup have been tested and work fine.The docs will need to be updated. I'm still expecting some of the details here to change, so they're left as is right now.The docs have now been updated to reflect the changes.