Definition mixins

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Definition mixins

Raul-Sebastian Mihăilă
I've been successfully experimenting with a kind of mixins which is different from the naive mixin solution (for instance based on Object.assign) and from functional mixins (https://medium.com/javascript-scene/functional-mixins-composing-software-ffb66d5e731c). I call them definition mixins.

The reason I'm bringing this up is that the definition mixins solution is verbose and custom syntax would be useful to simplify things.

Using traditional functions we can create constructors that hold private state. The main difference between definition mixins and the other ones is that the mixins share private state with the context where they are used. In this way, they are intimately connected to the context where the object is created. It's important to note that the constructor will choose what private state to share with the mixins.

Other differences:
- you don't get the gorilla-banana problem
- you don't get property collisions at all (unlike the other mixins where the last object always wins)
- beside sharing private state, another advantage is that you explicitly pick the functions that you want to mix in, so it's clear what the object's structure is by simply looking at its definition (which can be useful in a dynamic language like js).

Beside sharing private state, the other issues with the other kinds of mixins can be solved by using specific APIs with which you explicitly list the functions you're interested in.

Example:
Let's say we have a game of chess. We have a game board that is used to play the game and a building board that is used to create specific positions and then start a game from those positions. The game board and the building board are similar because they have similar state and logic to update that state (for instance whether the position is checkmate, stalemate etc.) because we want to create a game board from a building board and vice versa. They are also different because a simple board will have a move function that moves a piece and updates the state of the board. A possible move is en passant. You can not perform en passant in a building board, because the building board is not used for playing the game. The building board has a deletePiece function that is used to remove any number of pieces from the board anytime. The game board doesn't have such a function because the only way a piece can be removed during a game is by following the rules of chess. The game board also handles promoting pawns, while the building board doesn't.

```js
// board file
import * as boardMixin from '...';

function Board({board: fromBoard}) {
  const mix = {
    kings: {},
    currentMove
  };
  let promotingPawn = null;
  const board = boardMixin.create();

  board.currentSide = null;
  board.isPromoting = false;
  board.isCheckmate = false;
  board.isStalemate = false;
  board.isDraw = false;

  const isSafeMove = mix.isSafeMove = boardMixin.isSafeMove(mix);
  const setBoardResolution = mix.setBoardResolution = boardMixin.setBoardResolution(board, mix);

  board.isSquareAttacked = boardMixin.isSquareAttacked(board);
  board.getPiece = boardMixin.getPiece(board);

  board.move = (piece, x, y) => {
    // ...
    if (!isSafeMove(piece, x, y)) {
      return false;
    }

    setBoardResolution();
  };

  // ...
}
```

---

```js
// building-board file
import * as boardMixin from '...';

function BuildingBoard({board: fromBoard}) {
  const mix = {
    kings: {},
    currentMove: null
  };
  const board = boardMixin.create();

  board.currentSide = null;
  board.isCheckmate = false;
  board.isStalemate = false;
  board.isDraw = false;

  const setBoardResolution = mix.setBoardResolution = boardMixin.setBoardResolution(board, mix);

  board.getPiece = boardMixin.getPiece(board);

  board.isBoardPlayable = () => {
    // ...
  };

  board.deletePiece = (piece) => {
    // ...
    
    setBoardResolution();
  };
}
```

---

```js
// board-mixin file
// mixin functions:
const isSafeMove = (mix) =>
  (piece, x, y) => {
    // ...
  };

const setBoardResolution = (board, mix) =>
  () => {
    // ...
    board.isCheckmate = true;
  };

// ...
```

In this example we're creating mixin functions that become public or private methods in the constructor. The mix object is used to hold the mixin functions as a mixin function might want to use another mixin function. Also, in our example we're also holding in the mix object the private state that we wish to share with the mixin functions. The board object is an array of arrays and because we use it in the mixin functions we need to make sure that it's created before the mixin functions are mixed into the constructor. Mixin functions such as isSafeMove are also assigned to consts because we want to easily call them in the constructor, without having to use the mix object.

This is verbose. It would be niced if we could:
- create bindings for the mixin functions in the constructor more easily
- add the mixin functions to the mix object more easily
- create the board object at a later point in time, after the mixin functions are mixed in

Therefore I propose the following syntax and semantics:

```js
function F() {
  const privateState = 3;
  let privateStateObj = {};
  const obj = {};

  // method1 becomes a const binding. if the as syntax wasn't used, it would
  // have been named mixinFunc1.
  mixin obj, mixinFunc1 as method1: privateState, privateStateObj;
  // mixinFunc2 becomes a (public) method of obj                          
  mixin on obj, mixinFunc2: privateState;
  // method2 becomes a (public) method of obj. no private state is introduced
  // to the mixin function.
  mixin on obj, mixinFunc2 as method2;

  // method1 is the result of calling the mixinFunc1 mixin definition function
  // when the mixin line is evaluated,
  // namely function (x, y) { binding2.z = x + y; mix.mixinFunc2(4); }
  method1(1, 2); 
  // obj.mixinFunc2 is the result of calling the mixinFunc2 mixin definition function
  // when the mixin line is evaluated, namely function (x) { obj.z = x; }
  obj.mixinFunc2(3);

  return obj;
}

// obj, binding1, bindin2 are indirect bindings, meaning that their value can be changed from F
// and they are consts in mixinFunc1. The value of binding1 is the value of privateState and
// the value of binding2 is the value of privateStateObj.
// mix is const and a frozen object. 
// mix.mixinFunc1 exists because it was mixed in the constructor, as well as mix.mixinFunc2.
// note that mix.method1 doesn't exist because method1 is just a name used in F.
// obj.mixinFunc2 and obj.method2 exist.
function mixinFunc1(obj, mix, binding1, binding2) {
  return function (x, y) {
    binding2.z = x + y;

    mix.mixinFunc2(4);
  };
}

function mixinFunc2(obj, mix, binding) {
  return function (x) {
    obj.z = x;
  };
}
```

Because the mix object is created automatically, we can not use it to introduce private state to the mixin functions. Because the parameter bindings that the mixin definition functions (mixinFunc1, mixinFunc2) have are indirect bindings, we can create the obj object in the F constructor at a later point in time.

I imagine something like this could also be used with the class and private fields syntax.


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss