We build our first state transactions as two discrete transactions, each working on a specific portion of the state. Each of these transitions are governed by an acceptable range and we want to be able to apply these limit within our model.
With our discrete transactions we see that is easy to reason about and keeps our rules local to the data being worked on, without having to be considered in other transitions or, even worse, in the eventual view layer. Patterns like this make it easier to easily transport a given data model to any view layer.
const { curry, compose, State, mapProps } = require("crocks"); const { modify } = State; const state = { left: 8, moves: 0 }; const inc = x => x + 1; const dec = x => x - 1; // Make sure 'x' in range of [min, max] // if x < min, return min // if x > max, return max const limitRange = (min, max) => x => Math.min(Math.max(min, x), max); // Run the 'fn', get the value, then pass into limitRange const limitAfter = curry((min, max, fn) => compose( limitRange(min, max), fn ) ); // For each key we passed in, apply fn to it const over = (key, fn) => modify(mapProps({ [key]: fn })); const limitMoves = limitAfter(0, 8); const decLeft = () => over("left", limitMoves(dec)); const res = decLeft() .chain(decLeft) .execWith(state); console.log(res); //{left: 6, moves: 0}