Definition mixins
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.
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.
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
/*
* 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 +
'">▼</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;
}());
}());
Actually the mix obj must be extensible but must be an exotic object so that it's extended only through the mixin mechanism.
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.
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
/*
* 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 +
'">▼</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
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.
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;
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
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 themix
constant, and assigns its return value tomix.isSafeMove
and a new constantisSafeMove
(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.
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
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%
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%
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).
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
May I point out a few things:
-
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.
-
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.
-
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.
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:
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.
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.
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.
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.
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?
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.
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.
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
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.
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.
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?
---------- 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.
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:
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:
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.