简体   繁体   中英

How can you force functional programming immutability on minesweeper table?

I am reading a lot about functional programming and immutability, and have a bit of a problem comprehending how you can make even a simple minesweeper game by not mutating the state? It is obvious that you have an initial state of the minesweeper table in an array ([0,0,0,0] - all tiles are hidden). But how can you reveal the tile if you do not record that change in the state ([0,0,1,0] - first tile in the second row is open)??

Here is a simple example of what I'm saying:

let minesweeperTable2x2 = [0,0,0,0];

let revealTile = ( tileNumber ) => {
  minesweeperTable2x2[tileNumber] = 1;
}

console.log(minesweeperTable2x2); // [0,0,0,0]
revealTile(2);
console.log(minesweeperTable2x2); // [0,0,1,0]
revealTile(0);
console.log(minesweeperTable2x2); // [1,0,1,0]
revealTile(1);
console.log(minesweeperTable2x2); // [1,1,1,0]
revealTile(3);
console.log(minesweeperTable2x2); // [1,1,1,1]

Is there anybody who is into functional programming and immutability and can explain to me this by converting the code I wrote to functional programming paradigm, respecting full immutability???

You can re-create the table every time revealTile is called, and use that new table until it needs to be updated again:

 const initialTable = { ...[0,0,0,0] }; const revealTile = (lastTable, tileNumber) => { return { ...lastTable, [tileNumber]: 1 }; }; console.log(initialTable); const nextTable = revealTile(initialTable, 2); console.log(nextTable);

For an example of a slightly more complicated snippet that will run until all items of the array are revealed:

 const initialTable = { ...[0,0,0,0] }; const revealTile = (lastTable, tileNumber) => { return { ...lastTable, [tileNumber]: 1 }; }; const iter = (currentTable) => { const indexToReveal = prompt('Index to reveal? Currently ' + Object.values(currentTable)); const num = parseInt(indexToReveal); if (indexToReveal === null || indexToReveal < 0 || indexToReveal > 3) { throw new Error(); } const nextTable = revealTile(currentTable, indexToReveal); if (Object.values(nextTable).every(val => val === 1)) { console.log('Filled'); } else { iter(nextTable); } } iter(initialTable);

This way, you're not mutating the state, but you're creating a new state every time a change needs to happen, and then passing along the new state everywhere it needs to be used.

When working with immutability in mind the new state of the minesweeper is always returned by the transition function ( revealTile ), so that the given state is left intact.

 const revealTile = (n, minesweeper) => minesweeper.map( (value, i) => i === n ? 1 : value, ); // ### ---- const minesweeper = [0, 0, 0, 0]; console.log(minesweeper); // [0,0,0,0] console.log( revealTile(2, minesweeper), ); // [0,0,1,0]

Most functional programming languages manage immutability for you. JavaScript isn't such a language, so you need to handle it yourself. You could use a library like Immer or Immutable for this, but it's not always necessary. Without a library, the usual approach is:

  1. Create a copy of the object
  2. Change the copy
  3. Replace the original with the copy

In the case of updating a single element of an array, a straightforward implementation would look like this:

 function revealTile(table, tileNumber) { const nextTable = table.slice(); // create a copy nextTable[tileNumber] = 1; // update the copy return nextTable; // return the copy } let table = [0,0,0,0]; table = revealTile(table, 1); console.log(table); table = revealTile(table, 2); console.log(table); table = revealTile(table, 0); console.log(table); table = revealTile(table, 3); console.log(table);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM