squint-cljs / squint

Light-weight ClojureScript dialect
https://squint-cljs.github.io/squint
639 stars 38 forks source link

Protocols extended to nil #130

Open lilactown opened 2 years ago

lilactown commented 2 years ago

nil (i.e. JS null) can have protocols extended to them in Clojure(Script). However, in JS one cannot set or access properties on null, so the way we extend protocols to all other types does not work.

We could have a simple nil-protocol-table which we would set and look up the value on if nil was passed to extend-type and satisfies?

const nil_protocol_table = Object.create(null); // to avoid prototypes extended to `object`

export function satisfies_QMARK_(protocol, x) {
  if (x === null) {
    x = nil_protocol_table;
  }
  return x[protocol];
}

We would also need to add this same redirection to all protocol method functions we emit.

export const IFoo_bar = Symbol("IFoo_bar");
export function bar(foo, a, b) {
  if (foo === null) {
    let foo = nil_protocol_table;
  }
  return foo[IFoo_bar](foo, a, b);
}

While not a lot of code, it's more than double it was before, which is a bummer. Perhaps we could encapsulate this in a helper function.

export function lookup_protocol_method(protocol_method, x) {
  if (x === null) {
    let x = nil_protocol_table;
  };
  return x[protocol_method];
}

export function satisfies_QMARK_(protocol, x) {
  return lookup_protocol_method(protocol, x);
}

// in user code
export const IFoo_bar = Symbol("IFoo_bar");
export function bar(foo, a, b) {
  return lookup_protocol_method(IFoo_bar, foo)(foo, a, b);
}
borkdude commented 2 years ago

Not sure if this works nicely with treeshaking if you store protocols in a global table? I guess you could also store the nil methods on a per-protocol object

On Thu, 25 Aug 2022 at 16:28, Will Acton @.***> wrote:

nil (i.e. JS null) can have protocols extended to them in Clojure(Script). However, in JS one cannot set or access properties on null, so the way we extend protocols to all other types does not work.

We could have a simple nil-protocol-table which we would set and look up the value on if nil was passed to extend-type and satisfies?

const nil_protocol_table = Object.create(null); // to avoid prototypes extended to object export function satisfiesQMARK(protocol, x) { if (x === null) { x = nil_protocol_table; } return x[protocol];}

We would also need to add this same redirection to all protocol method functions we emit.

export const IFoo_bar = Symbol("IFoo_bar");export function bar(foo, a, b) { if (foo === null) { let foo = nil_protocol_table; } return foo[IFoo_bar](foo, a, b);}

While not a lot of code, it's more than double it was before, which is a bummer. Perhaps we could encapsulate this in a helper function.

export function lookup_protocol_method(protocol_method, x) { if (x === null) { let x = nil_protocol_table; }; return x[protocol_method];} export function satisfiesQMARK(protocol, x) { return lookup_protocol_method(protocol, x);} // in user codeexport const IFoo_bar = Symbol("IFoo_bar");export function bar(foo, a, b) { return lookup_protocol_method(IFoo_bar, foo)(foo, a, b);}

— Reply to this email directly, view it on GitHub https://github.com/clavascript/clavascript/issues/130, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACFSBQLEKNGYVSXUKSIQK3V257IVANCNFSM57TK2WUA . You are receiving this because you are subscribed to this thread.Message ID: @.***>

-- https://www.michielborkent.nl https://www.eetvoorjeleven.nu

lilactown commented 2 years ago

yeah, we could store it on a per-protocol object too. I'll think more about that.

borkdude commented 2 years ago

Is a Symbol an object as well? Maybe we could abuse the protocol symbol for it.

On Thu, 25 Aug 2022 at 17:12, Will Acton @.***> wrote:

yeah, we could store it on a per-protocol object too. I'll think more about that.

— Reply to this email directly, view it on GitHub https://github.com/clavascript/clavascript/issues/130#issuecomment-1227395237, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACFSBX3RTRCEMA7DORBAQDV26EOLANCNFSM57TK2WUA . You are receiving this because you commented.Message ID: @.***>

-- https://www.michielborkent.nl https://www.eetvoorjeleven.nu

lilactown commented 2 years ago

It doesn't seem to work like I would hope

Welcome to Node.js v18.7.0.
Type ".help" for more information.
> let s = Symbol("IFoo")
undefined
> s["foo"] = true
true
> s["foo"]
undefined
borkdude commented 2 years ago

I believe that in CLJS information about extension is stored on the protocol object, not on the extended types.

This is what happens when you extend to string and null:

goog.object.set(cljs.user.Foo,"string",true);

goog.object.set(cljs.user.foo,"string",(function (_){
  return "str";
}));
goog.object.set(cljs.user.Foo,"null",true);

goog.object.set(cljs.user.foo,"null",(function (_){
  return "nil";
}));
borkdude commented 2 years ago

So the protocol method foo is both a function and an object that holds a mapping of type (as string) to the implementation.