Skip to content
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

Protocols #21

Closed
lilactown opened this issue Aug 15, 2022 · 8 comments
Closed

Protocols #21

lilactown opened this issue Aug 15, 2022 · 8 comments

Comments

@lilactown
Copy link
Collaborator

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 Symbols 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);
  }
}
@lilactown
Copy link
Collaborator Author

To clarify: the above idea could accomplish the following goals:

  1. Emit far less code than we do today
  2. Allow JS code to easily import and extend ClavaScript protocols
  3. Allow ClavaScript code to use JS protocols like the transducer example

@borkdude
Copy link
Member

borkdude commented Aug 15, 2022

@lilactown Sounds like good goals, but need to learn a bit more:

  1. 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?
  2. Is it really that much different from what clava generates now (based on code borrowed from CLJS)?
  3. Which transducer example?

@lilactown
Copy link
Collaborator Author

  1. Symbol.for is an explicit lookup in the global symbol table. It is different than someone randomly calling the object.bar method, which may have a conflict. The other thing that symbols ensure is that the properties don't show up in things like Object.keys and JSON.stringify, which is an improvement over using a long obscure string.
  2. 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.
  3. It's linked in the OP: Official spec for transformer protocol cognitect-labs/transducers-js#20

@lilactown
Copy link
Collaborator Author

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/

@borkdude
Copy link
Member

@lilactown I've read the articles and this seems the way to go forward, agreed!

@lilactown lilactown mentioned this issue Aug 18, 2022
@borkdude
Copy link
Member

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.

@lilactown lilactown mentioned this issue Aug 23, 2022
3 tasks
@lilactown
Copy link
Collaborator Author

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.

This was referenced Aug 24, 2022
@lilactown
Copy link
Collaborator Author

Closing this as the general approach seems to work.

Additional issues will be opened for gaps & bugs.

borkdude added a commit that referenced this issue Dec 24, 2022
borkdude added a commit that referenced this issue Dec 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants