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

Mixin issues #29

Open
raulsebastianmihaila opened this issue Nov 14, 2017 · 9 comments
Open

Mixin issues #29

raulsebastianmihaila opened this issue Nov 14, 2017 · 9 comments

Comments

@raulsebastianmihaila
Copy link

I think that the fact that protocols are also a kind of mixins is problematic. First, because they are a naive kind of mixins and, secondly, because I think it would be better to separate the two mechanisms. These are the issues with this kind of mixins:

  • the gorilla-banana problem: you get everything the protocol contains even though you might not need everything
  • property collisions: the fact that the first mixin always wins (in case the property is not overwritten by the class) isn't really a solution
  • when looking at a class definition you can't tell what methods you have/inherit.
  • when you see an inherited method in one of your own methods, you can't tell where it's coming from (you have to look at all the protocols you're implementing potentially)
  • you can not share private state. sharing private state is useful when you're reusing code to define a concept that has private state

I have a proposal for a different kind of mixins that don't have all these issues. They also work with both function syntax and class syntax and I believe they can be made to work with protocols. This way the protocol and mixin mechanisms can stay separate.

Please see here a more detailed ongoing discussion.

@Jamesernator
Copy link

Jamesernator commented Nov 14, 2017

Despite appearances the protocols proposed aren't using strings by default for example this:

protocol Fizz {
    buzz;
}

class Foo implements Fizz {
    buzz() {

    }
}

Is not a valid implementation of the protocol Fizz (in fact it throws an error for being not implemented), Fizz.buzz is actually a Symbol not a string so in order to implement it you need to use:

class Foo implements Fizz {
    [Fizz.buzz]() {

    }
}

new Foo().buzz // undefined
new Foo()[Fizz.buzz] // [Function: Fizz.buzz]
Fizz.buzz // Symbol("Fizz.buzz")

With this most of your points don't apply (unless using the string based version which is there for backwards compatibility with existing protocols not necessarily for general use):

  • Property collisions can't happen because the protocol symbols are unique
  • Similarly looking at a class definition you definitely know what methods are inherited from the mixin
  • And as consequence you always know what you inherit as methods must be accessed via the Symbol keys

Now I don't really have suggestions for the gorilla-banana problem other than to just not worry about it, if your protocols have lots of fields then this might be a design flaw with the protocol but if it isn't then there's surely a reason the protocol requires those fields? Something that is probably needed is optional fields when not everything is required to be implemented e.g. maybe something like (not sure if this is what you were getting at):

// Supposing Iterator was implemented using the proposed protocol
protocol Iterator {
    next;
    return?; // Still creates Iterator.return 
             // but doesn't require implementation
    throws?; // Ditto
}

class ArrayIterator implements Iterator {
    [Iterator.next]() { /* Not optional */ }
    [Iterator.return]() { /* Optional, could be emitted */ }
}

And regarding the private state thing, I raised a point about this a while ago, however I don't think it's super critical at least in the initial designs of this proposal given that it's generally bad practice to poke and mess with arbitrary symbol properties on objects.

@raulsebastianmihaila
Copy link
Author

raulsebastianmihaila commented Nov 14, 2017

Property collisions can't happen because the protocol symbols are unique

In fact, the same symbol can be used in different protocols. See this issue

Similarly looking at a class definition you definitely know what methods are inherited from the mixin

What do you mean? You can not do that if you inherit the implementations. You have to look in the protocols themselves.

And as consequence you always know what you inherit as methods must be accessed via the Symbol keys

That is only if you implement the actual method yourself.

All these, together with sharing private state, are probably not important issues for protocols since probably protocols will mostly not be used as mixins, although they could easily be abused like that. My point was that since they can work as mixins, it would be better to have a more capable and better designed mixin mechanism and use that for protocols as well.

@Jamesernator
Copy link

In fact, the same symbol can be used in different protocols. See this issue

Yes but this is ultimately a design decision, reusing a Symbol (inside a protocol or not) risks collision. I'd have to see a concrete example of the problem in #20 to see that you'd ever want to do this in practice in a way that would cause collisions.

What do you mean? You can not do that if you inherit the implementations. You have to look in the protocols themselves.

Well I mean you know that it inherits everything that the protocol defines, by definition you already have access to the protocol to implement it so given that fact you can simply use whatever is available. There's nothing different from subclassing here, you might need to inspect the chain upwards to discover all things a class has I don't see anyway around this e.g.:

const bar = Symbol('bar')

class A {
   foo() {

   }

   [bar]() {}
}

protocol B {
   fizz() {
       return 12
   }
}

// For both A and B you simply have to inspect their definition to know
// what you're getting, there's nothing special about protocol in this
// regard, you simply have to inspect the rest of the chain
class C extends A implements B {}

That is only if you implement the actual method yourself.

I don't understand what you mean, pre-defined ones will simply exist on the thing that implements the protocol:

protocol Mixin {
    mixinMethod() {
        return 10;
    }
}

class Foo implements Mixin {}

new Foo()[Mixin.mixinMethod]() // 10

if (someArbitraryValue implements Mixin) {
    // If something implements a protocol it gets
    // pre-defined methods for free
    someArbitraryValue[Mixin.mixinMethod]() // 10
}

@raulsebastianmihaila
Copy link
Author

raulsebastianmihaila commented Nov 14, 2017

Yes but this is ultimately a design decision, reusing a Symbol (inside a protocol or not) risks collision. I'd have to see a concrete example of the problem in #20 to see that you'd ever want to do this in practice in a way that would cause collisions.

I was just saying you can get collisions, I'm not saying it's ok to share the symbols.

Well I mean you know that it inherits everything that the protocol defines, by definition you already have access to the protocol to implement it so given that fact you can simply use whatever is available. There's nothing different from subclassing here, you might need to inspect the chain upwards to discover all things a class has I don't see anyway around

Sure, inheritance has this issue as well. In fact, this kind of mixins are a sort of multiple inheritance.

Again, my point is that if a generic well designed mixin mechanism is added later (and I wish it was sooner), it would be better for the protocols to use it instead of having yet another kind of inheritance.

@michaelficarra
Copy link
Member

@raulsebastianmihaila What is it that you would like out of a "mixin" feature that you don't feel protocols provide? I would like for this proposal to satisfy the community's desires for mixins. The way I understand it, mixins are simply protocols with no required symbols. All of the points in your original comment have been addressed, and the "gorilla-banana problem" you're referring to doesn't seem to cause an actual issue: there's no downside to having additional symbol-named properties.

@raulsebastianmihaila
Copy link
Author

@michaelficarra In essence I want mixins to be based on composition and to allow sharing private state with the mixin functions. My understanding is that the protocols are some sort of contracts, which I think is a good idea. Mixins, on the other hand, are for reusing code. These are two different concerns. I don't see code reuse as the primary purpose of protocols. Also the primary purpose (if at all) of mixins is not to impose a contract. I'm not certain I understand what you mean by 'the points have been addressed' as it can be intepreted in two different ways (they're either acknowledged or they're solved). I certainly don't think the issues are solved.

If protocols are only used for implementing contracts, then I don't consider these issues to be critical and because of this I can consider them to have been addressed (in both ways). These issues, however, are very important for mixins (if protocols are used as a general mixin mechanism).

  1. Being able to easily tell what your class/object can do by simply looking at its definition context (and not have to jump to different files) is very useful in a dynamic language.

  2. Also being able to easily tell where its capabilities come from by simply looking at its definition context is important in a dynamic language.

  3. While protocols make significant use of symbols, mixins will more likely use string keys as they are easier to work with. There is a greater likelihood for string based property collisions to occur.

  4. In a protocol, extra properties are unlikely to be a big problem. In a mixin context, extra properties are a violation of the principle of least knowledge. Using a method just because it's available -- while from a rigorous design based perspective the method shouldn't be used -- is an abuse. Having extra methods that you shouldn't use in certain cases means that the code doesn't express the design intentions. This problem can be fixed by breaking the protocol in multiple small sized protocols. The greater the granularity, the greater the flexibility. This breakage looks similar to composition and so composition is a natural way of avoiding this issue.

All these 4 issues are solved in a composition based mixin mechanism by explicitly picking every item that you need from the mixin providers.

The last (5th) issue with protocols is the inability to share private state. This might not be important or there may be ways around this for contracts. But since mixins are ways of reusing partial definitions of system concepts, while these concepts will use private state, it's important to be able to use the private state in these definitions. Of course, since private state shouldn't be accessible for everybody, the private state needs to be explicitly introduced in trustworthy mixin providers from the context where the mixins are mixed in.

To address (solve) all these issues, I came up with a composition based mixins proposal that allows sharing private state. I call these mixins definition mixins. The syntax looks as follows:

function mixin1(obj, mix, privObj) {
  return (v) => obj.q + mix.mixin2(3, privObj.m);
}

function mixin2(obj) {
  return (x, y) => x + y + obj.z;
}

class Destination {
  #privObj = {m: 100};
  q = 3;
  z = 1000;

  mixin this:
    mixin1,
    mixin2:
      this.#privObj;

  method() {
    this.#mixin1(35);
  }
}

More details about the syntax/semantics can be found in the github proposal or in this esdiscuss thread. An important detail is that this mechanism also works in a function context, where constant bindings are created and where closure based private state can be used. The mixins can also be public methods of the context object. Please let me know if anything regarding my proposal is not clear.

I think it's important to be clear about whether the protocols mechanism is meant to be used as a general purpose mixin mechanism and not only for contracts. If they are a general mixin mechanism, it has all these disadvantages in comparison to my proposal. If it's mainly for contracts and it also uses a mixin mechanism in order to provide default implementations, then 1) we should have a separate more capable and well designed mixin mechanism that can be used outside protocols and 2) it would make more sense for the protocols to use the same mixin mechanism, instead of using a different mixin mechanism.

I also worry that if there is no separate mixin mechanism the protocols will be abused as mixins and as I showed, it would not be fun.

Definition mixins could be used together with protocols. The protocols could then be used to impose a contract, while the mixin mechanism can be used to pick the default implementations from the protocols.

protocol P {
  a() { /* ... */ }
}

class C implements P {
  mixin on this: P.a;
}

This way the protocols could also use private state.

@raulsebastianmihaila
Copy link
Author

raulsebastianmihaila commented Nov 19, 2017

I missed the fact that protocols can not provide default implementations for properties with string keys. Sorry for that. That means that property collisions are indeed very unlikely to happen with protocols. However this also makes them less feasible as a general mixin mechanism, as symbols are more difficult to work with than strings.

@aluanhaddad
Copy link

aluanhaddad commented Nov 19, 2017

Without the ability to implement string keys, protocols cannot serve the purpose of general mixins.

The detractions to ergonomics, discoverability, and transparency implied by Symbol keys will make using protocols as mixins DOA.

However, from reading the recent meeting notes it seems that protocols are intended to subsume mixins.

@michaelficarra
Copy link
Member

@aluanhaddad How would you expect a mixin system based on string-named properties to handle collisions?

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

4 participants