Definition mixins

# Raul-Sebastian Mihăilă (7 years ago)

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 ( 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.

// 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();
  };

  // ...
}

// 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();
  };
}

// 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:

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.

# Raul-Sebastian Mihăilă (7 years ago)

One issue that just occurred to me is that we want different families of mixin functions to have access to different mix objects, so that one family doesn't have access to mixin functions from another family (since they're private and the constructor must decide what private data shares with the mixin functions). So different mix object could be created for different families based on the reference's base value (by reference I mean the reference that is used to specify the mixin function).

mixin obj, mixinModule.func1;
mixin obj, mixinModule.func2;
mixin obj, func3;

Here func1 and func2 will use the same mix object and func3 will have another mix object.

Another solution would be, since normally mixin functions are imported from other modules, to have the source module of the mixin function as the grouping key for the mixin functions, even if the base value of the reference is an environment record (like in func3's case). If the mixin functions belong to the current module it's fine for them to share the private state.

# Raul-Sebastian Mihăilă (7 years ago)

Also, in the same context, for different mixin associated objects there will be different mix objects:

function F() {
  const obj = {};
  const obj2 = {};

  mixin obj, mixin1;
  mixin obj2, mixin2;
}

function mixin1() {
  return (obj, mix) => {};
}

function mixin2() {
  return (obj, mix) => {};
}

The mix argument that the mixin function created by mixin2 receives will be different from the one received by the mixin function created by mixin1 because obj1 !== obj2.

# kai zhu (7 years ago)

the problem is that you chose to write the chess program in javascript, instead of say, python or c#.

why did you choose javascript? probably because you intend the chess program to be an online webapp. mixins, like classes, are inferior to plain json-objects for webapps. how do you intend to serialize the mixin board-state so you can sync it with a friend in an online match? the i/o part of a webapp is typically as challenging as the business-logic of the game itself.

it would be more efficient if you represented the board-state as a json-object, so it can be easily serailized and shared with other online players, and use static functions to manipulate the json-data.

here's a functional web-demo of a simple connect4 game using only json-objects and static functions (in 400 lines of code).

-kai

kaizhu256/node-connect4

/*
 * test.js
 *
 * this file contains the standalone connect-4 game
 *
 * setup instructions
 * 1. save this file as test.js
 * 2. install nodejs
 * 3. run the shell command
 *    $ PORT=8081 node test.js
 * 4. open browser to url http://localhost:8081
 * 5. play the connect4 game!
 */



/*jslint
    bitwise: true,
    browser: true,
    maxerr: 8,
    maxlen: 96,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';
    var local;



    // run shared js-env code - pre-init
    (function () {
        // init local
        local = {};
        // init modeJs
        local.modeJs = (function () {
            try {
                return typeof navigator.userAgent === 'string' &&
                    typeof document.querySelector('body') === 'object' &&
                    typeof XMLHttpRequest.prototype.open === 'function' &&
                    'browser';
            } catch (errorCaughtBrowser) {
                return module.exports &&
                    typeof process.versions.node === 'string' &&
                    typeof require('http').createServer === 'function' &&
                    'node';
            }
        }());
        // init global
        local.global = local.modeJs === 'browser'
            ? window
            : global;
        local.nop = function () {
        /*
         * this function will do nothing
         */
            return;
        };
        // export local
        local.global.local = local;
    }());



    // run shared js-env code - function
    (function () {
        local.gameStateCreate = function () {
        /*
         * this function will create a new game state
         */
            var state;
            state = {};
            state.board = [
                // -> rows
                [0, 0, 0, 0, 0, 0], // |
                [0, 0, 0, 0, 0, 0], // v
                [0, 0, 0, 0, 0, 0], //
                [0, 0, 0, 0, 0, 0], // c
                [0, 0, 0, 0, 0, 0], // o
                [0, 0, 0, 0, 0, 0], // l
                [0, 0, 0, 0, 0, 0]  // s
            ];
            state.playerCurrent = 1;
            state.streakToWin = 4;
            return state;
        };

        local.playerMove = function (state, positionCol) {
        /*
         * this function will perform a move
         * by dropping the state.playerCurrent's disc in the given positionCol,
         * and then checks to see if it wins the game
         */
            var colList, ii, positionRow, streak;
            if (state.ended) {
                state.error = new Error('game ended');
            }
            if (state.error) {
                // debug error
                console.error(state.error.stack);
                return;
            }
            if (positionCol === 'random') {
                while (true) {
                    positionCol = Math.floor(Math.random() *
state.board.length);
                    colList = state.board[positionCol] || [];
                    if (colList[colList.length - 1] === 0) {
                        break;
                    }
                }
            }
            state.positionCol = positionCol;
            colList = state.board[positionCol] || [];
            // naive algorithm to deposit disc in the last unfilled
positionRow in colList
            for (ii = 0; ii < colList.length; ii += 1) {
                if (colList[ii] === 0) {
                    positionRow = ii;
                    colList[positionRow] = state.playerCurrent;
                    // debug board
                    console.log(state.board.join('\n'));
                    break;
                }
            }
            if (positionRow === undefined) {
                state.error = new Error('invalid move');
                // debug error
                console.error(state.error.stack);
                return;
            }
            // naive algorithm to check for win condition in the column
            // e.g.
            // [
            // -> rows
            // [1, 1, 1, 1, 0, 0], // |
            // [2, 2, 2, 0, 0, 0], // v
            // [0, 0, 0, 0, 0, 0], //
            // [0, 0, 0, 0, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the column
            for (ii = 0; ii < colList.length; ii += 1) {
                if (colList[ii] === state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check for win condition in the row
            // e.g.
            // [
            // -> rows
            // [1, 2, 0, 0, 0, 0], // |
            // [1, 2, 0, 0, 0, 0], // v
            // [1, 2, 0, 0, 0, 0], //
            // [1, 0, 0, 0, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the row
            for (ii = 0; ii < state.board.length; ii += 1) {
                if (state.board[ii][positionRow] === state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check for win condition in the upward diagonal
            // e.g.
            // [
            // -> rows
            // [1, 2, 0, 0, 0, 0], // |
            // [2, 1, 0, 0, 0, 0], // v
            // [2, 1, 1, 0, 0, 0], //
            // [2, 2, 1, 1, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the row
            for (ii = 0; ii < state.board.length; ii += 1) {
                if (state.board[ii][positionRow + ii - positionCol] ===
                        state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check for win condition in the
downward diagonal
            // e.g.
            // [
            // -> rows
            // [2, 2, 1, 1, 0, 0], // |
            // [2, 1, 1, 0, 0, 0], // v
            // [2, 1, 0, 0, 0, 0], //
            // [1, 2, 0, 0, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the row
            for (ii = 0; ii < state.board.length; ii += 1) {
                if (state.board[ii][positionRow - ii + positionCol] ===
                        state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check if game ends in a draw
            if (state.board.every(function (colList) {
                    return colList[colList.length - 1] !== 0;
                })) {
                state.ended = 0;
                return;
            }
            // switch player for next move
            state.playerCurrent = state.playerCurrent === 1
                ? 2
                : 1;
        };
    }());
    switch (local.modeJs) {



    // run browser js-env code - post-init
    case 'browser':
        local.domGameBoard = document.querySelector('#gameBoard1');
        local.gameDraw = function (state) {
        /*
         * this function will draw the current state of the game
         */
            var board, tmp;
            tmp = '';
            // game ended with a draw
            if (state.ended === 0) {
                tmp += 'game is a draw!';
                tmp += '<div class="playerDisc"></div>';
            } else {
                // game ended with a win
                if (state.ended) {
                    tmp += 'player ' + state.playerCurrent + ' has won!';
                // game is ongoing
                } else {
                    tmp += 'player ' + state.playerCurrent + '\'s turn';
                    if (state.error && state.error.message === 'invalid move') {
                        tmp += ' <span style="color: #f00;">(invalid

move, retry!)</span>';
                    }
                }
                tmp += '<div class="playerDisc playerDisc' +
state.playerCurrent + '"></div>';
            }
            document.querySelector('#gameStatus1').innerHTML = tmp;
            // remove error
            state.error = null;
            // transpose board
            board = state.board[0].map(function (_, ii) {
                // jslint-hack
                local.nop(_);
                return state.board.map(function (colList) {
                    return colList[ii];
                });
            }).reverse();
            board = '<table>\n' +
                '<thead>' + board[0].map(function (_, ii) {
                    // jslint-hack
                    local.nop(_);
                    return '<th><button data-position-col="' + ii +
'">&#x25BC;</button></th>';
                }).join('') + '</thead>\n' +
                '<tbody>' + board.map(function (rowList) {
                    return '<tr>' + rowList.map(function (playerDisc) {
                        return '<td><div class="playerDisc playerDisc' +
                            playerDisc + '"></div></td>';
                    }).join('') + '</tr>';
                }).join('\n') + '</tbody></table>';
            local.domGameBoard.innerHTML = board;
        };
        local.testRun = function (event) {
            switch (event && event.currentTarget.id) {
            case 'gameBoard1':
                // perform player move
                if (event.target.dataset.positionCol) {
                    local.playerMove(local.gameState,
Number(event.target.dataset.positionCol));
                    local.gameDraw(local.gameState);
                }
                break;
            // reset game
            case 'resetButton1':
                local.gameState = local.gameStateCreate();
                local.gameDraw(local.gameState);
                break;
            }
        };
        // init event-handling
        ['click'].forEach(function (event) {
            Array.prototype.slice.call(
                document.querySelectorAll('.on' + event)
            ).forEach(function (element) {
                element.addEventListener(event, local.testRun);
            });
        });
        // reset game
        document.querySelector('#resetButton1').click();
        break;



    // run node js-env code - post-init
    case 'node':
        // require modules
        local.http = require('http');
        local.fs = require('fs');
        try {
            local.utility2 = require('utility2');
        } catch (ignore) {
        }
        // save assets-script
        local.assetsScript = local.fs.readFileSync(__filename, 'utf8');
        // init server
        local.server = local.http.createServer(function (request, response) {
            // debug
            console.log('handling request ' + request.url);
            // serve assets-script
            if (request.url.lastIndexOf('test.js') >= 0) {
                response.end(local.assetsScript);
                return;
            }
            // serve main-page
            /* jslint-ignore-begin */
            response.end('\
<html lang="en">\n\
<head>\n\
<meta charset="UTF-8">\n\
<title>connect4</title>\n\
<style>\n\
#gameBoard1 thead button:hover {\n\
    cursor: pointer;\n\
}\n\
#gameBoard1 thead {\n\
    margin-bottom: 10px;\n\
}\n\
#gameBoard1 table {\n\
    background-color: #77f;\n\
}\n\
.playerDisc {\n\
    background-color: #fff;\n\
    border: 1px solid black;\n\
    border-radius: 20px;\n\
    height: 20px;\n\
    margin: 5px;\n\
    width: 20px;\n\
}\n\
.playerDisc1 {\n\
    background-color: #ff0;\n\
}\n\
.playerDisc2 {\n\
    background-color: #f00;\n\
}\n\
</style>\n\
</head>\n\
<body>\n\
<h1><a href="https://github.com/kaizhu256/node-connect4">connect-4

game</a></h1>\n\
<h4><a download href="test.js">download standalone app</a></h4>\n\
<button class="onclick" id="resetButton1">reset game</button><br>\n\
<br>\n\
<h2 id="gameStatus1"></h2>\n\
<div id="gameContainer1">\n\
    <div class="onclick" id="gameBoard1"></div>\n\
</div>\n\
<script src="test.js"></script>\n\
</body>\n\
            ');
            /* jslint-ignore-end */
        });
        local.server.on('error', function (error) {
            if (error.code === 'EADDRINUSE' && !local.EADDRINUSE) {
                local.EADDRINUSE = error;
                local.PORT = Number(local.PORT) + 1;
                local.server.listen(local.PORT, function () {
                    console.log('server listening on port ' + local.PORT);
                });
                return;
            }
            throw error;
        });
        local.PORT = process.env.PORT || 8081;
        local.server.listen(local.PORT, function () {
            console.log('server listening on port ' + local.PORT);
        });
        break;
    }



    // run shared js-env code - post-init
    (function () {
        return;
    }());
}());
# Raul-Sebastian Mihăilă (7 years ago)

Actually the mix obj must be extensible but must be an exotic object so that it's extended only through the mixin mechanism.

# Raul-Sebastian Mihăilă (7 years ago)

On Sun, Nov 5, 2017 at 5:27 PM, kai zhu <kaizhu256 at gmail.com> wrote:

the problem is that you chose to write the chess program in javascript, instead of say, python or c#.

Kai, you're off-topic. The topic is mixins, not chess games. Please refrain from disrupting this thread.

# Michał Wadas (7 years ago)

Saying "you don't need this if you follow my personal way of doing things" isn't helpful at all. Especially when these views are controversial.

On 5 Nov 2017 4:27 pm, "kai zhu" <kaizhu256 at gmail.com> wrote:

the problem is that you chose to write the chess program in javascript, instead of say, python or c#.

why did you choose javascript? probably because you intend the chess program to be an online webapp. mixins, like classes, are inferior to plain json-objects for webapps. how do you intend to serialize the mixin board-state so you can sync it with a friend in an online match? the i/o part of a webapp is typically as challenging as the business-logic of the game itself.

it would be more efficient if you represented the board-state as a json-object, so it can be easily serailized and shared with other online players, and use static functions to manipulate the json-data.

here's a functional web-demo of a simple connect4 game using only json-objects and static functions (in 400 lines of code).

-kai

kaizhu256/node-connect4

/*
 * test.js
 *
 * this file contains the standalone connect-4 game
 *
 * setup instructions
 * 1. save this file as test.js
 * 2. install nodejs
 * 3. run the shell command
 *    $ PORT=8081 node test.js
 * 4. open browser to url http://localhost:8081
 * 5. play the connect4 game!
 */



/*jslint
    bitwise: true,
    browser: true,
    maxerr: 8,
    maxlen: 96,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';
    var local;



    // run shared js-env code - pre-init
    (function () {
        // init local
        local = {};
        // init modeJs
        local.modeJs = (function () {
            try {
                return typeof navigator.userAgent === 'string' &&
                    typeof document.querySelector('body') === 'object' &&
                    typeof XMLHttpRequest.prototype.open === 'function' &&
                    'browser';
            } catch (errorCaughtBrowser) {
                return module.exports &&
                    typeof process.versions.node === 'string' &&
                    typeof require('http').createServer === 'function' &&
                    'node';
            }
        }());
        // init global
        local.global = local.modeJs === 'browser'
            ? window
            : global;
        local.nop = function () {
        /*
         * this function will do nothing
         */
            return;
        };
        // export local
        local.global.local = local;
    }());



    // run shared js-env code - function
    (function () {
        local.gameStateCreate = function () {
        /*
         * this function will create a new game state
         */
            var state;
            state = {};
            state.board = [
                // -> rows
                [0, 0, 0, 0, 0, 0], // |
                [0, 0, 0, 0, 0, 0], // v
                [0, 0, 0, 0, 0, 0], //
                [0, 0, 0, 0, 0, 0], // c
                [0, 0, 0, 0, 0, 0], // o
                [0, 0, 0, 0, 0, 0], // l
                [0, 0, 0, 0, 0, 0]  // s
            ];
            state.playerCurrent = 1;
            state.streakToWin = 4;
            return state;
        };

        local.playerMove = function (state, positionCol) {
        /*
         * this function will perform a move
         * by dropping the state.playerCurrent's disc in the given
positionCol,
         * and then checks to see if it wins the game
         */
            var colList, ii, positionRow, streak;
            if (state.ended) {
                state.error = new Error('game ended');
            }
            if (state.error) {
                // debug error
                console.error(state.error.stack);
                return;
            }
            if (positionCol === 'random') {
                while (true) {
                    positionCol = Math.floor(Math.random() *
state.board.length);
                    colList = state.board[positionCol] || [];
                    if (colList[colList.length - 1] === 0) {
                        break;
                    }
                }
            }
            state.positionCol = positionCol;
            colList = state.board[positionCol] || [];
            // naive algorithm to deposit disc in the last unfilled
positionRow in colList
            for (ii = 0; ii < colList.length; ii += 1) {
                if (colList[ii] === 0) {
                    positionRow = ii;
                    colList[positionRow] = state.playerCurrent;
                    // debug board
                    console.log(state.board.join('\n'));
                    break;
                }
            }
            if (positionRow === undefined) {
                state.error = new Error('invalid move');
                // debug error
                console.error(state.error.stack);
                return;
            }
            // naive algorithm to check for win condition in the column
            // e.g.
            // [
            // -> rows
            // [1, 1, 1, 1, 0, 0], // |
            // [2, 2, 2, 0, 0, 0], // v
            // [0, 0, 0, 0, 0, 0], //
            // [0, 0, 0, 0, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the column
            for (ii = 0; ii < colList.length; ii += 1) {
                if (colList[ii] === state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check for win condition in the row
            // e.g.
            // [
            // -> rows
            // [1, 2, 0, 0, 0, 0], // |
            // [1, 2, 0, 0, 0, 0], // v
            // [1, 2, 0, 0, 0, 0], //
            // [1, 0, 0, 0, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the row
            for (ii = 0; ii < state.board.length; ii += 1) {
                if (state.board[ii][positionRow] === state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check for win condition in the upward
diagonal
            // e.g.
            // [
            // -> rows
            // [1, 2, 0, 0, 0, 0], // |
            // [2, 1, 0, 0, 0, 0], // v
            // [2, 1, 1, 0, 0, 0], //
            // [2, 2, 1, 1, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the row
            for (ii = 0; ii < state.board.length; ii += 1) {
                if (state.board[ii][positionRow + ii - positionCol] ===
                        state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check for win condition in the
downward diagonal
            // e.g.
            // [
            // -> rows
            // [2, 2, 1, 1, 0, 0], // |
            // [2, 1, 1, 0, 0, 0], // v
            // [2, 1, 0, 0, 0, 0], //
            // [1, 2, 0, 0, 0, 0], // c
            // [0, 0, 0, 0, 0, 0], // o
            // [0, 0, 0, 0, 0, 0], // l
            // [0, 0, 0, 0, 0, 0]  // s
            // ]
            streak = 0;
            // iterate through the row
            for (ii = 0; ii < state.board.length; ii += 1) {
                if (state.board[ii][positionRow - ii + positionCol] ===
                        state.playerCurrent) {
                    streak += 1;
                    if (streak >= 4) {
                        state.ended = state.playerCurrent;
                        return;
                    }
                } else {
                    streak = 0;
                }
            }
            // naive algorithm to check if game ends in a draw
            if (state.board.every(function (colList) {
                    return colList[colList.length - 1] !== 0;
                })) {
                state.ended = 0;
                return;
            }
            // switch player for next move
            state.playerCurrent = state.playerCurrent === 1
                ? 2
                : 1;
        };
    }());
    switch (local.modeJs) {



    // run browser js-env code - post-init
    case 'browser':
        local.domGameBoard = document.querySelector('#gameBoard1');
        local.gameDraw = function (state) {
        /*
         * this function will draw the current state of the game
         */
            var board, tmp;
            tmp = '';
            // game ended with a draw
            if (state.ended === 0) {
                tmp += 'game is a draw!';
                tmp += '<div class="playerDisc"></div>';
            } else {
                // game ended with a win
                if (state.ended) {
                    tmp += 'player ' + state.playerCurrent + ' has won!';
                // game is ongoing
                } else {
                    tmp += 'player ' + state.playerCurrent + '\'s turn';
                    if (state.error && state.error.message === 'invalid
move') {
                        tmp += ' <span style="color: #f00;">(invalid

move, retry!)</span>';
                    }
                }
                tmp += '<div class="playerDisc playerDisc' +
state.playerCurrent + '"></div>';
            }
            document.querySelector('#gameStatus1').innerHTML = tmp;
            // remove error
            state.error = null;
            // transpose board
            board = state.board[0].map(function (_, ii) {
                // jslint-hack
                local.nop(_);
                return state.board.map(function (colList) {
                    return colList[ii];
                });
            }).reverse();
            board = '<table>\n' +
                '<thead>' + board[0].map(function (_, ii) {
                    // jslint-hack
                    local.nop(_);
                    return '<th><button data-position-col="' + ii +
'">&#x25BC;</button></th>';
                }).join('') + '</thead>\n' +
                '<tbody>' + board.map(function (rowList) {
                    return '<tr>' + rowList.map(function (playerDisc) {
                        return '<td><div class="playerDisc playerDisc' +
                            playerDisc + '"></div></td>';
                    }).join('') + '</tr>';
                }).join('\n') + '</tbody></table>';
            local.domGameBoard.innerHTML = board;
        };
        local.testRun = function (event) {
            switch (event && event.currentTarget.id) {
            case 'gameBoard1':
                // perform player move
                if (event.target.dataset.positionCol) {
                    local.playerMove(local.gameState,
Number(event.target.dataset.positionCol));
                    local.gameDraw(local.gameState);
                }
                break;
            // reset game
            case 'resetButton1':
                local.gameState = local.gameStateCreate();
                local.gameDraw(local.gameState);
                break;
            }
        };
        // init event-handling
        ['click'].forEach(function (event) {
            Array.prototype.slice.call(
                document.querySelectorAll('.on' + event)
            ).forEach(function (element) {
                element.addEventListener(event, local.testRun);
            });
        });
        // reset game
        document.querySelector('#resetButton1').click();
        break;



    // run node js-env code - post-init
    case 'node':
        // require modules
        local.http = require('http');
        local.fs = require('fs');
        try {
            local.utility2 = require('utility2');
        } catch (ignore) {
        }
        // save assets-script
        local.assetsScript = local.fs.readFileSync(__filename, 'utf8');
        // init server
        local.server = local.http.createServer(function (request, response)
{
            // debug
            console.log('handling request ' + request.url);
            // serve assets-script
            if (request.url.lastIndexOf('test.js') >= 0) {
                response.end(local.assetsScript);
                return;
            }
            // serve main-page
            /* jslint-ignore-begin */
            response.end('\
<html lang="en">\n\
<head>\n\
<meta charset="UTF-8">\n\
<title>connect4</title>\n\
<style>\n\
#gameBoard1 thead button:hover {\n\
    cursor: pointer;\n\
}\n\
#gameBoard1 thead {\n\
    margin-bottom: 10px;\n\
}\n\
#gameBoard1 table {\n\
    background-color: #77f;\n\
}\n\
.playerDisc {\n\
    background-color: #fff;\n\
    border: 1px solid black;\n\
    border-radius: 20px;\n\
    height: 20px;\n\
    margin: 5px;\n\
    width: 20px;\n\
}\n\
.playerDisc1 {\n\
    background-color: #ff0;\n\
}\n\
.playerDisc2 {\n\
    background-color: #f00;\n\
}\n\
</style>\n\
</head>\n\
<body>\n\
<h1><a href="https://github.com/kaizhu256/node-connect4">connect-4

game</a></h1>\n\
<h4><a download href="test.js">download standalone app</a></h4>\n\
<button class="onclick" id="resetButton1">reset game</button><br>\n\
<br>\n\
<h2 id="gameStatus1"></h2>\n\
<div id="gameContainer1">\n\
    <div class="onclick" id="gameBoard1"></div>\n\
</div>\n\
<script src="test.js"></script>\n\
</body>\n\
            ');
            /* jslint-ignore-end */
        });
        local.server.on('error', function (error) {
            if (error.code === 'EADDRINUSE' && !local.EADDRINUSE) {
                local.EADDRINUSE = error;
                local.PORT = Number(local.PORT) + 1;
                local.server.listen(local.PORT, function () {
                    console.log('server listening on port ' + local.PORT);
                });
                return;
            }
            throw error;
        });
        local.PORT = process.env.PORT || 8081;
        local.server.listen(local.PORT, function () {
            console.log('server listening on port ' + local.PORT);
        });
        break;
    }



    // run shared js-env code - post-init
    (function () {
        return;
    }());
}());

On 11/5/17, Raul-Sebastian Mihăilă <raul.mihaila at gmail.com> wrote:

Also, in the same context, for different mixin associated objects there will be different mix objects:

function F() {
  const obj = {};
  const obj2 = {};

  mixin obj, mixin1;
  mixin obj2, mixin2;
}

function mixin1() {
  return (obj, mix) => {};
}

function mixin2() {
  return (obj, mix) => {};
}

The mix argument that the mixin function created by mixin2 receives will

be

# Christopher Thorn (7 years ago)

JSON is not a good serialization format for chess positions or games. We have FEN and PGN for those use cases. Implementing the "business logic" for chess is quite a lot more challenging than implementing FEN or PGN (de)serialization.

Mixins are well-suited for writing chess software in JavaScript. For instance, a Knight is a "leaper", Bishops, Rooks and Queens are "riders". People have imagined many fairy pieces^1, which mix various combinations of traits.

# Raul-Sebastian Mihăilă (7 years ago)

I wrote a first draft of the spec for the proposal ( raulsebastianmihaila/ecma262-definition-mixins-proposal). In some cases I'm not certain about the accuracy of the spec and I also cut the corners in a few places. There are 2 versions. The second version only contains the differences from the first version. Version 1 is simpler. Version 2 uses indirect bindings in a mixin function provider call and these semantics don't exist in the language. I didn't try to spec them as I thought it would be challenging (probably involving creating a new kind of function). If this is not pragmatic it's fine, version 1 doesn't have indirect bindings. I like version 2 a bit more, but version 1 would work as well.

Any feedback from people from TC39 or people interested in mixins is appreciated.

Compare the following two fragments, one with current ES syntax/semantics, and the other one with the mixin syntax/semantics (from the first simpler version of the spec).

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);
const mixState = {
  kings: {},
  currentMove
};
let promotingPawn = null;
const board = boardMixin.create();

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

mixin board:
  boardMixin.isSafeMove,
  boardMixin.setBoardResolution:
    mixState;

mixin on board:
  boardMixin.isSquareAttacked,
  boardMixin.getPiece;
# T.J. Crowder (7 years ago)

On Mon, Nov 13, 2017 at 8:45 AM, Raul-Sebastian Mihăilă < raul.mihaila at gmail.com> wrote:

I wrote a first draft of the spec for the proposal...

Could you add a "Motivations" section outlining why this is needed and what alternatives currently exist without new syntax?

Separately, without yet having delved into the details, various parts of the "without mixins" code look suspect, for example:

const isSafeMove = mix.isSafeMove = boardMixin.isSafeMove(mix);

That calls the boardMixin.isSafeMove function, passing it the mix constant, and assigns its return value to mix.isSafeMove and a new constant isSafeMove (which confusingly doesn't seem to be used anywhere). Should it really be calling the function? Not binding or something?

-- T.J. Crowder

# Raul-Sebastian Mihăilă (7 years ago)

On Mon, Nov 13, 2017 at 10:54 AM, T.J. Crowder <tj.crowder at farsightsoftware. com> wrote:

Could you add a "Motivations" section outlining why this is needed and what alternatives currently exist without new syntax?

It would be indeed worth adding such a section in the proposal, but for now please take a look at the first message in this thread in which I do mention why this syntax is useful and current alternatives.

Separately, without yet having delved into the details, various parts of the "without mixins" code look suspect, for example:

const isSafeMove = mix.isSafeMove = boardMixin.isSafeMove(mix);

That calls the boardMixin.isSafeMove function, passing it the mix constant, and assigns its return value to mix.isSafeMove and a new constant isSafeMove (which confusingly doesn't seem to be used anywhere). Should it really be calling the function? Not binding or something?

-- T.J. Crowder

Please do delve into the details (especially the first message in this thread). The first message shows how the mix object is used. Without mixins syntax, the mix object has two purposes, it holds the mixin functions that are provided by mixin function providers so that you can call a mixin function in another mixin function. This mix object is passed to the mixin function providers, so that the mixin functions that they create can use the object. However, since an essential part of this proposal is the ability to share private state with the mixin functions, the mix object also contains the private state to be shared.

The reason the isSafeMove constant is created is to be able to easily call the function in the current context, without having to use the mix object. It's a nice to have. The mixin syntax not only makes this possible, but it also provides the separation between holding the mixin functions in an object so that they are accessible in the mixin functions themselves and sharing whatever private data needs to be shared with the mixin functions. This separation would have also been possible without the mixin syntax. However, the syntax is much simpler with this proposal: the constant binding is created, or the function can become a public method of the object, you don't need to create and handle the mix object and this also works with private fields in a class context. Also, the second version of the proposal is based on the possibly controversial idea of indirect bindings in a function context.

# Bob Myers (7 years ago)

You should review the TypeScript approach to mixins: www. typescriptlang.org/docs/handbook/mixins.html.

But more generally, mixins are a very specific, opinionated OO design pattern. They are probably misused more often than not. If you think that big class hierarchies were brittle and hard to maintain, just wait until you include mixins. There are many, many alternatives to modeling things like "can move more than one square at a time in some direction", and most of them are, at least in my opinion, superior to mixins. It is also not too hard to make the case that mixins are somewhere between difficult and impossible to implement properly in a language which doesn't have proper built-in support--the fundamental notion of prototypes in JS, which underlies its classes, can be considered mixin-hostile.

Bob

# Raul-Sebastian Mihăilă (7 years ago)

On Mon, Nov 13, 2017 at 1:54 PM, Bob Myers <rtm at gol.com> wrote:

You should review the TypeScript approach to mixins: www.typescriptlang.org/docs/handbook/mixins.html.

This seems very similar to the naive approach based on Object.assign that I mention in the first message of this thread. Please see the disadvantages that I mention there and also note that this approach doesn't provide the ability to share private state.

But more generally, mixins are a very specific, opinionated OO design pattern. They are probably misused more often than not. If you think that big class hierarchies were brittle and hard to maintain, just wait until you include mixins.

Could you please provide some more details? The advantages of my approach to mixins in comparison to other approaches that I mentioned in the first message also apply in comparison to inheritance.

There are many, many alternatives to modeling things like "can move more than one square at a time in some direction", and most of them are, at least in my opinion, superior to mixins.

The chess example is just an example. What mixins achieve in general is sharing a partial definition of a concept in multiple places. Can you please mention a few of the many, many superior alternatives to achieve this (please note I'm also interested in using private state)?

It is also not too hard to make the case that mixins are somewhere between difficult and impossible to implement properly in a language which doesn't have proper built-in support--the fundamental notion of prototypes in JS, which underlies its classes, can be considered mixin-hostile.

Did you take a look at the spec I provided? The first version is based 100%

# Raul-Sebastian Mihăilă (7 years ago)

For some reason, esdiscuss.org doesn't render my last sentence entirely. This is what I wrote:

Did you take a look at the spec I provided? The first version is based 100%

# Raul-Sebastian Mihăilă (7 years ago)

The first version is based a hundred percent on concepts and mechanisms already used in Ecma262. (Looks like esdiscuss.org has an issue with percentages).

# T.J. Crowder (7 years ago)

On Mon, Nov 13, 2017 at 12:25 PM, Raul-Sebastian Mihăilă < raul.mihaila at gmail.com> wrote:

The first version is based a hundred percent on concepts and mechanisms already used in Ecma262. (Looks like esdiscuss.org has an issue with percentages).

There's an Edit link on messages on esdiscuss.org to let you fix issues like that (e.g., when there's some esdiscuss.org-specific issue).

-- T.J. Crowder

# Isiah Meadows (7 years ago)

May I point out a few things:

  1. The React community, who has for most of its existence used mixins and pseudo-proxies (they call them 'higher order components", but they're basically component proxies) is now moving to this thing called a "render prop", which is basically a function argument returning children. This is because mixins don't compose nor scale.

  2. Elm, a compile-to-JS language/front-end framework (it's both), separates the model from the view, and the view is a function from model to vnode tree. When you use a child component, you store the child's model in the parent model, and you pass that state to the child's view when rendering the parent's view. There exist JS microframeworks that implement this flow for JS as well.

  3. It's common practice in Angular to use dependency injection and directives where most others would use mixins. Angular dropped mixin support because of similar scalability issues.

The whole point of these examples is to state that mixins aren't critical enough to be added to the language. It's perfectly fine as a library, but it's not something we should include in the language.


Oh, and fun fact, some languages, like Elm and Haskell, don't even provide means for creating mixins, even through interfaces. Instead, they use functions exclusively to define extended functionality.

# Raul-Sebastian Mihăilă (7 years ago)

On Mon, Nov 13, 2017 at 4:25 PM, T.J. Crowder <tj.crowder@ farsightsoftware.com> wrote:

There's an Edit link on messages on esdiscuss.org to let you fix issues like that (e.g., when there's some esdiscuss.org-specific issue).

Cool, thanks!

On Mon, Nov 13, 2017 at 4:48 PM, Isiah Meadows <isiahmeadows at gmail.com>

wrote:

May I point out a few things:

  1. The React community, who has for most of its existence used mixins and pseudo-proxies (they call them 'higher order components", but they're basically component proxies) is now moving to this thing called a "render prop", which is basically a function argument returning children. This is because mixins don't compose nor scale.

  2. Elm, a compile-to-JS language/front-end framework (it's both), separates the model from the view, and the view is a function from model to vnode tree. When you use a child component, you store the child's model in the parent model, and you pass that state to the child's view when rendering the parent's view. There exist JS microframeworks that implement this flow for JS as well.

  3. It's common practice in Angular to use dependency injection and directives where most others would use mixins. Angular dropped mixin support because of similar scalability issues.

The whole point of these examples is to state that mixins aren't critical enough to be added to the language. It's perfectly fine as a library, but it's not something we should include in the language.

It's good that you're bringing these up. First, I think communities aren't (and probably shouldn't always be) so cohesive. It would be interesting to see what percentage of React users use the render prop thing. Assuming that there is such a movement, this shows an evolution and I don't see any reason to believe that the render prop is the end of it. Therefore, I don't think it's safe to conclude that what we have now is good enough and that because it's good enough we don't need anything else. It's important to note that the React mixins are quite different from what I propose. They have all the issues that I mention in the first message. If they didn't have those issues, they would have probably had a different life.

What you say about Elm sounds good. I don't see how it's related to this topic. I'm not saying use mixins for everything. However, you might want to (not necessarily in Elm) use mixins to partially define some of your models, in order to reuse code, without running into issues that you could get with inheritance. I'm definitely not using mixins for views, and if I think about it not even for controllers, but I do find them very useful for models.

If some sort of mixins proved to not be useful for certain things, it doesn't mean that they aren't useful for anything. I don't know how Angular used mixins (I'm not experienced with Angular2). Are those Typescript mixins? Then they share some of the issues with the React mixins. It's difficult to say how much of this bad experience people have had with mixins is due to the concept itself, to the particularities of the mixin approaches or to the fact that some people prefer a more functional approach and run away from everything that looks object oriented. I haven't seen a mixin approach in Javascript similar to this approach and that doesn't have at least some of the issues I listed in the beginning of this thread and I think it's important to be aware of this when balancing out mixins.

Oh, and fun fact, some languages, like Elm and Haskell, don't even provide means for creating mixins, even through interfaces. Instead, they use functions exclusively to define extended functionality.

That's good, not all languages are the same. Yet, Javascript is an object oriented general purpose programming language. Not only for UIs. Perhaps a separate topic regarding the direction TC39 wants to take WRT to OOP vs functional programming would be interesting. I personally think Javascript should continue to support both. Reusing code is essential and the basic ways of doing this in an OO style are composition and inheritance. Mixins, in this approach, are another kind of composition.

# Raul-Sebastian Mihăilă (7 years ago)

I need to reword one phrase I wrote: I haven't seen a mixin approach in Javascript similar to this approach or one that doesn't have at least some of the issues I listed in the beginning of this thread and I think it's important to be aware of this when balancing out mixins.

# Raul-Sebastian Mihăilă (7 years ago)

I updated the proposal with sections for motivation, comparison with other kinds of mixins and protocols. raulsebastianmihaila/ecma262-definition-mixins-proposal

The protocols, in their current form, are a naive kind of mixin imlementation. I think it's better to separate contracts from mixins and it's possible to use them together for using the default implementations that the protocols may provide.

Would any TC39 member like to champion this proposal?

# Isiah Meadows (7 years ago)

Protocols are not mixins - they're interfaces. They specify minimum behavior required to implement (their primary focus), not simply extended behavior. In particular, protocols and mixins would be more complementary than conflicting.

# Raul-Sebastian Mihăilă (7 years ago)

On Sat, Nov 18, 2017 at 7:19 PM, Isiah Meadows <isiahmeadows at gmail.com>

wrote:

Protocols are not mixins - they're interfaces. They specify minimum behavior required to implement (their primary focus), not simply extended behavior. In particular, protocols and mixins would be more complementary than conflicting.

Protocols are mixins as well, take a look at this comment. michaelficarra/proposal-first-class-protocols#29 Also, I didn't say they conflict with mixins, I said they should be separate.

# Bob Myers (7 years ago)

I'm a JS programmer with more decades of experience than I care to mention, and follow developments in language design closely. Frankly, I don't understand what you are trying to accomplish or why support for it needs to be in the language. I strongly doubt I'm the only one. Rather than spending your time tilting at TC39 windmills, I'd suggest a focus on putting together a library which implements your vision of mixins.

Bob

# Raul-Sebastian Mihăilă (7 years ago)

On Sat, Nov 18, 2017 at 7:53 PM, Bob Myers <rtm at gol.com> wrote:

I'm a JS programmer with more decades of experience than I care to mention, and follow developments in language design closely. Frankly, I don't understand what you are trying to accomplish or why support for it needs to be in the language. I strongly doubt I'm the only one. Rather than spending your time tilting at TC39 windmills, I'd suggest a focus on putting together a library which implements your vision of mixins.

I explained thoroughly why it would be good for it to be in the language, to the extent that I think you simply don't want to understand what I'm trying to accomplish. It's a very useful and popular OO feature that was attempted many times in user code and implementing it in the language allows for better semantics and simpler syntax. In particular, user code doesn't have access to the [[ScriptOrModule]] slot that I'm using in my proposal. Also, having a standard version is better. Otherwise, you can argue the same about many other things in the language.

Why would this be "tilting at TC39 windmills"? Esdiscuss is the right place to discuss proposals and I think your stance is inappropriate. This is even more surprising as TC39 is currently discussing a proposal that includes a mixin mechanism.

We already started a conversation that you left suspended. Unfortunately your second intervention isn't constructive either.

# Bob Myers (7 years ago)

Unfortunately your second intervention isn't constructive either.

Great, I'll bow out. Do let me know when you find your champion, at which point I'll follow further developments with keen interest.

# dante federici (7 years ago)

I'm having a little trouble parsing your proposed syntax. Can you formalize it a bit better? Am I write in guessing that it's more like sugar around bind and composition?

Like, what's going on here?

mixin obj, mixinFunc1 as method1: privateState, privateStateObj;

I guess I'm wondering why this would be a part of the language and not just helper functions and a library? Maybe it'd be helpful to represent your chess example using your proposed syntax...?

Some questions (that may be moot once I actually understand your syntax):

  • How does it actually prevent colliding names?
  • How does it interact with closures?
  • How does it prevent mixins from colliding over shared state?
# Raul-Sebastian Mihăilă (7 years ago)

---------- Forwarded message ---------- From: dante federici <c.dante.federici at gmail.com> To: es-discuss at mozilla.org Cc: Bcc: Date: Mon, 27 Nov 2017 22:22:11 +0000 Subject: Re: Re: Definition mixins I'm having a little trouble parsing your proposed syntax. Can you formalize it a bit better? Am I write in guessing that it's more like sugar around bind and composition?

Like, what's going on here?

mixin obj, mixinFunc1 as method1: privateState, privateStateObj;

I posted a link to a more formal proposal in one of the previous messages: raulsebastianmihaila/ecma262-definition-mixins-proposal I updated the syntax in the meantime as indeed this initial one looks confusing. So, instead, it is:

mixin obj: mixinFunc1, mixinFunc2 as method2: privateState1, privateState2;

or you could write it like this:

mixin obj:
    mixinFunc1,
    mixinFunc2 as method2:
        privateState1, privateState2;

raulsebastianmihaila/ecma262-definition-mixins-proposal#syntax-and-semantics

For the semantics, please see the example in the github spec and the semantics section: raulsebastianmihaila/ecma262-definition-mixins-proposal#looks

I guess I'm wondering why this would be a part of the language and not just helper functions and a library? Maybe it'd be helpful to represent your chess example using your proposed syntax...?

The most difficult part of implementing it as a library is managing the mix objects that are passed to the mixin function providers. For instance:

class Context1 {
  #privateState = {x: 100};

  mixin this:
    mixinFunc1,
    mixinFunc2:
      this.#privateState;

  mixin on this: mixinFunc3;

  method() {
    /* ... */
    this.#mixinFunc1(35);
    this.mixinFunc3();
  }
}

If mixinFunc1, mixinFunc2 come from the same module/script then they share the mix object, so that mixinFunc1 can call mixinFunc2. If mixinFunc3 comes from a different module, then it mustn't have access to the other mixin functions. (Of course, if mixinFunc1 and mixinFunc2 become public methods of the object, for instance if the mixin on syntax was used, then mixinFunc3 would have access to them since it has access to the context object.) You could provide an API to mix in functions that come from different places (of course in this case you would need to be more aware about where they come from, which in some cases might be a nuisance), and the API could create the mix object and pass it. But what if the state you want to share with mixinFunc1 is different from the one you share with mixinFunc2? You have to create the mix object yourself and that can get ugly. The way the proposal solves this is by using the [[ScriptOrModule]] slot that functions have.

Some questions (that may be moot once I actually understand your syntax):

  • How does it actually prevent colliding names?

You pick only the functions you're interested in. If two mixin function provider expressions resolve to the same identifier name then you can avoid conflicts by using the as syntax to give a custom name to the functions. You could benefit from early errors semantics to get an error if you forget to do that.

  • How does it interact with closures?

The way any regular objects and functions interact with closures, because indeed it's largely just syntactic sugar.

  • How does it prevent mixins from colliding over shared state?

You choose what state you share with any of the mixin functions:

function mixinProvider1(obj, mix, state) { return () => {}; }

function mixinProvider2(obj, mix, state1, state2) { return () => {}; }

class Class {
  #privateState1 = {};
  #privateState2 = {};

  mixin this: mixinProvider1: this.#privateState1;
  mixin this: mixinProvider2: this.#privateState1, this.#privateState2;
}

If you think about all the possible scenarios you will observe that standard syntax is much simpler than an API. Note that I also imagined a second version for the semantics that you can find in the github spec.