added stop(..) to walk(..) + examples...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-09-28 04:25:44 +03:00
parent 3aa6577eb9
commit 61b57bca2a

205
diff.js
View File

@ -263,17 +263,23 @@ var proxy = function(path, func){
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// //
// Construct a reusable walker function... // Construct a reusable walker function...
// walk(get(..)) // walk(get(..))
// -> func(start, ...nodes) // -> func(state, ...nodes)
//
// Construct a reusable walker function with set initial state...
// walk(get(..), state)
// -> func(...nodes)
// //
// Walk the nodes... // Walk the nodes...
// walk(get(..), start, ...nodes) // walk(get(..), state, ...nodes)
// -> res // -> res
// //
// The get(..) that walk(..) expects has the following signature: // The get(..) that walk(..) expects has the following signature:
// get(res, node, next(..), down(..)) // get(res, node, next(..), down(..), stop(..))
// -> res // -> res
// //
// Queue next nodes to walk... // Queue next nodes to walk...
@ -286,41 +292,90 @@ var proxy = function(path, func){
// state. // state.
// //
// Walk the next set of nodes... // Walk the next set of nodes...
// down(start, ...nodes) // down(state, ...nodes)
// -> res // -> res
// NOTE: this will process all the nodes and then return the // NOTE: this will process all the nodes and then return the
// result. // result.
// //
// Stop the walk...
// stop()
// stop(result)
// -> undefined
// NOTE: this will break current function execution in a similar
// effect to 'return', i.e. no code in function will be
// executed after stop(..) call...
// NOTE: this is done by throwing a special error, this error
// should either not be caught or must be re-thrown, as
// shadowing this exception may have unexpected results...
//
// //
// Example: // Example:
// // sum all the values of a nested array... // // sum all the values of a nested array...
// var sum = walk(function(res, node, next){ // var sum = walk(function(res, node, next){
// // go into arrays... // return node instanceof Array ?
// if(node instanceof Array){ // // compensate for that next(..) returns undefined...
// next(...node) // next(...node)
// // || res
// // sum the values... // : res + node }, 0)
// } else {
// console.log(node)
// res += node
// }
// return res
// })
// //
// // depth first walker... // // depth first walker...
// var sumd = walk(function(res, node, next, down){ // var sumd = walk(function(res, node, next, down, stop){
// if(node instanceof Array){ // return node instanceof Array ?
// res = down(res, ...node) // down(res, ...node)
// : res + node }, 0)
// //
// } else { // sum([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 3, 2, 4, 5
// console.log(node) // sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5
// res += node //
// } //
// return res // Example:
// }) // // XXX res/path threading seem unnatural here...
// var __firstZero = walk(function(res, node, next, down, stop){
// var k = node[0]
// var v = node[1]
// var path = res[0]
// return v === 0 ?
// // NOTE: we .slice(1) here to remove the initial null
// // we added in firstZero(..)...
// stop([ path.slice(1).concat([k]) ])
// : v instanceof Object?
// down(
// [path.concat([k]), null],
// ...Object.entries(v))
// : res }, [[], null])
// var firstZero = function(value){
// // NOTE: we are wrapping the input here to make it the same
// // format as that of Object.entries(..) items...
// return __firstZero([null, value]).pop() }
//
//
// // same as the above but illustrates a different strategy, a bit
// // cleaner but creates a walker every time it's called...
// var firstZero = function(value){
// return walk(
// function(res, node, next, down, stop){
// var k = node[0]
// var v = node[1]
// var path = res[0]
// return v === 0 ?
// // NOTE: we .slice(1) here to remove the initial null
// // we added in firstZero(..)...
// stop([ path.slice(1).concat([k]) ])
// : v instanceof Object?
// down(
// [path.concat([k]), null],
// ...Object.entries(v))
// : res },
// [[], null],
// // wrap the input to make it compatible with Object.entries(..)
// // items...
// [null, value])
// // separate the result from path...
// .pop() }
//
//
// firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y']
// //
// sum([1, [2], 3, [[4, 5]]]) // -> 15 and log 1, 3, 2, 4, 5
// sumd([1, [2], 3, [[4, 5]]]) // -> 15 and log 1, 2, 3, 4, 5
// //
// //
// NOTE: a walker is returned iff walk(..) is passed a single argument. // NOTE: a walker is returned iff walk(..) is passed a single argument.
@ -329,60 +384,92 @@ var proxy = function(path, func){
// walk(get)(start, ...nodes) // walk(get)(start, ...nodes)
// NOTE: walk goes breadth first... // NOTE: walk goes breadth first...
// //
var walk = function(get, state, ...args){ // XXX might be a good idea to move this into it's own module...
var _step = function(args, res){ // generic-walk might be a good name...
var walk = function(get, state, ...nodes){
// this is used to break out of the recursion...
// NOTE: this can leak out but we only care about it's identity thus
// no damage can be done...
var WalkStopException
var err_res
var _step = function(nodes, res){
// construct a comfortable env for the user and handle the // construct a comfortable env for the user and handle the
// results... // results...
var _get = function(node){ var _get = function(node){
var next = [] var next = []
res = get(res, node, res = get(
res,
node,
// breadth first step... // breadth first step...
// next(...nodes) // next(...nodes) -> undefined
// -> undefined
// NOTE: this is different to down(..) in that this will
// return immediately and no result is known at time
// of call thus undefined is returned...
// NOTE: this is different to down(..) in that the user
// indirectly gives this the state by returning it
// from the last get(..) called per level.
function(...nodes){ next = nodes }, function(...nodes){ next = nodes },
// depth first step... // depth first step...
// down(state, ..nodes) // down(state, ..nodes) -> res
// -> res
// NOTE: this is different to next as the user has control
// over what value to start with and will get the
// result they can handle...
function(res, ...nodes){ function(res, ...nodes){
return _step(nodes, res) }) return _step(nodes, res) },
// stop...
// stop()
// stop(value)
//
// NOTE: 'throw' is used to stop all handling, including
// the rest of the current level...
function(r){
err_res = r
WalkStopException = new Error('WALK_STOP_EVENT')
throw WalkStopException
})
return next return next
} }
return args.length == 0 ?
return nodes.length == 0 ?
// no new nodes to walk... // no new nodes to walk...
res res
// do the next level... // do the next level...
// NOTE: see note below... ( ;) ) // NOTE: see note below... ( ;) )
: _step(args : _step(nodes
.map(_get) .map(_get)
.reduce(function(next, e){ .reduce(function(next, e){
return e instanceof Array ? return e instanceof Array ?
next.concat(e) next.concat(e)
: next.push(e) }, []), res) } : next.push(e) }, []), res)
}
// call _step(..) and handle WalkStopException...
var _walk = function(nodes, res){
try{
return _step(nodes, res)
return arguments.length == 1 ? } catch(e){
// handle the abort condition...
if(e === WalkStopException){
return err_res
}
// something broke...
throw e
}
}
return (
// return a reusable walker... // return a reusable walker...
// NOTE: this wrapper is here so as to isolate and re-order res arguments.length == 1 ?
// construction and passing it to the next level... // NOTE: this wrapper is here so as to isolate and re-order res
// this is needed as when we do: // construction and passing it to the next level...
// step(res, ...args.map(_get).reduce(...)) // this is needed as when we do:
// res (if immutable) will first get passed by value and // step(res, ...nodes.map(_get).reduce(...))
// then will get re-assigned in _get(..), thus step(..) will // res (if immutable) will first get passed by value and
// not see the updated version... // then will get re-assigned in _get(..), thus step(..) will
// This approach simply pushes the pass-by-value of res till // not see the updated version...
// after it gets updated by _get(..). // This approach simply pushes the pass-by-value of res till
function(res, ...args){ // after it gets updated by _get(..).
return _step(args, res) } function(res, ...nodes){
return _walk(nodes, res) }
// reusable walker with a curried initial state...
// NOTE: better keep this immutable or clone this in get(..)
: arguments.length == 2 ?
function(...nodes){
return _walk(nodes, state) }
// walk... // walk...
: _step(args, res) : _walk(nodes, state))
} }