squint
squint copied to clipboard
Protocols
Right now, protocols generate a LOT of code. This makes it sort of hard to justify using it for a lot of standard library stuff.
In JS practice, protocol-like extensions are done using Symbol
s that are added to objects as properties. The symbols allow one to guarantee that there will be no conflicts with other code extending it.
The way I think this could work is like the following. For code like:
(defprotocol IFoo
(bar [this])
(baz [this a])
(baz [this a b])
(baz [this a b & more]))
It would generate the following code:
var IFoo = Symbol.for("my.ns.IFoo");
var IFoo_bar = Symbol.for("my.ns.IFoo/bar");
var IFoo_baz = Symbol.for("my.ns.IFoo/baz");
function bar(o) {
assert_method(o, IFoo_bar);
return o[IFoo_bar](o);
}
function baz(o, ...args) {
assert_method(o, IFoo_baz);
return o[IFoo_baz].apply(o, o, args);
}
Extension would mutate the object, adding symbol as a property:
(extend-type MyClass
IFoo
(bar [mc] :bar)
(baz [mc a] [:baz a])
(baz [mc a b] [:baz a b])
(baz [mc a b & more] (into [:baz a b] more))
MyClass[IFoo] = true;
MyClass[IFoo_bar] = function (mc, a) { return ["baz", a]; };
MyClass[IFoo_baz] = function (mc, ...args) {
if (args.length === 1) {
return ["baz", args[0]];
} else if (args.length === 2) {
return ["baz", args[0], args[1]];
} else if (args.length > 2) {
let [a, b, ...more] = args;
return into(["baz", a, b], more);
} else {
throw new Error("Invalid arity: " + args.length);
}
}
To clarify: the above idea could accomplish the following goals:
- Emit far less code than we do today
- Allow JS code to easily import and extend ClavaScript protocols
- Allow ClavaScript code to use JS protocols like the transducer example
@lilactown Sounds like good goals, but need to learn a bit more:
- What is the benefit of using a symbol over a string here? When you create Symbol/for + a same string you will have an identical symbol and thus a conflict, similar when you would just use the string?
- Is it really that much different from what clava generates now (based on code borrowed from CLJS)?
- Which transducer example?
-
Symbol.for
is an explicit lookup in the global symbol table. It is different than someone randomly calling theobject.bar
method, which may have a conflict. The other thing that symbols ensure is that the properties don't show up in things likeObject.keys
andJSON.stringify
, which is an improvement over using a long obscure string. - It's fairly similar in semantics, but right now Clava generates a lot more code than I wrote above. The code I wrote out also moves all handling of arity into the method implementation, rather than the protocol wrapper, which is more JS-like.
- It's linked in the OP: https://github.com/cognitect-labs/transducers-js/issues/20
Other examples of built-in "protocols" that could guide us towards a more JS-like design: https://dmitripavlutin.com/detailed-overview-of-well-known-symbols/
@lilactown I've read the articles and this seems the way to go forward, agreed!
This idea excites me: https://twitter.com/borkdude/status/1560711024336150528
Bringing your own immutable collections and add support via protocols for assoc
etc.
This would still get you the features from CLJS but in an "unbundled" way.
An amendment to the initial design: by using Symbol("Foo")
instead of Symbol.for("Foo")
, we no longer have to worry about collisions, since symbols constructed the former way have unique identities each time.
Closing this as the general approach seems to work.
Additional issues will be opened for gaps & bugs.