Created
February 7, 2012 20:10
-
-
Save dherman/1761665 to your computer and use it in GitHub Desktop.
turning recursion into iteration in JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Version 1. Simple recursive function. Blows the stack for large nodes. | |
function replace(node, from, to) { | |
switch (node.type) { | |
case IF: | |
return { | |
type: IF, | |
test: replace(node.test, from, to), | |
then: replace(node.then, from, to), | |
else: replace(node.else, from, to) | |
}; | |
case ID: | |
return { | |
type: ID, | |
name: node.name.replace(from, to) | |
}; | |
case SEQ: | |
return { | |
type: SEQ, | |
nodes: node.nodes.map(function(node) { | |
return replace(node, from, to); | |
}); | |
}; | |
// etc. | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Version 2. CPS. Still blows the stack because of lack of proper tail calls. | |
function replace(node, from, to, k) { | |
switch (node.type) { | |
case IF: | |
return replace(node.test, from, to, function(testNode) { | |
return replace(node.then, from, to, function(thenNode) { | |
return replace(node.else, from, to, function(elseNode) { | |
return k({ | |
type: IF, | |
test: testNode, | |
then: thenNode, | |
else: elseNode | |
}); | |
}); | |
}); | |
}); | |
case ID: | |
return k({ | |
type: ID, | |
name: node.name.replace(from, to) | |
}); | |
case SEQ: | |
return mapCPS(node.nodes, function(node, k) { | |
return replace(node, from, to, k); | |
}, k); | |
// etc. | |
} | |
} | |
function mapCPS(a, f, k) { | |
var i = 0, n = a.length; | |
var result = []; | |
return (function loop() { | |
if (i > n) | |
return k(result); | |
return f(a[i], function(x) { | |
result[i] = x; | |
i++; | |
return loop(); | |
}); | |
})(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Version 3. Iterative. No more modularization; one monolithic loop. | |
const TOP = 0, | |
IF_TEST = 1, | |
IF_THEN = 2, | |
IF_ELSE = 3, | |
SEQ_ENTRY = 4; | |
function replace(node, from, to) { | |
var stack = [{ type: TOP }]; | |
var result; | |
recursion: | |
while (stack.length) { | |
pushing: | |
while (true) { | |
switch (node.type) { | |
case IF: | |
stack.push({ | |
type: IF_TEST, | |
then: node.then, | |
else: node.else | |
}); | |
node = node.test; | |
continue pushing; | |
case ID: | |
result = { | |
type: ID, | |
name: node.replace(from, to); | |
}; | |
break pushing; | |
case SEQ: | |
if (!node.nodes.length) { | |
result = node; | |
break pushing; | |
} | |
stack.push({ | |
type: SEQ_ENTRY, | |
length: node.nodes.length, | |
nodes: node.nodes, | |
index: 0, | |
results: [] | |
}); | |
node = node.nodes[0]; | |
continue pushing; | |
// etc. | |
} | |
} | |
popping: | |
while (true) { | |
var top = stack.pop(); | |
switch (top.type) { | |
case TOP: | |
return result; | |
case IF_TEST: | |
node = top.then; | |
stack.push({ | |
type: IF_THEN, | |
test: result, | |
else: top.else | |
}); | |
continue recursion; | |
case IF_THEN: | |
node = top.else; | |
stack.push({ | |
type: IF_ELSE, | |
test: top.test, | |
then: result | |
}); | |
continue recursion; | |
case IF_ELSE: | |
result = { | |
type: IF, | |
test: top.test, | |
then: top.then, | |
else: result | |
}; | |
continue popping; | |
case SEQ_ENTRY: | |
if (top.index >= top.length) { | |
result = top.entries; | |
continue popping; | |
} | |
top.results[top.index] = result; | |
stack.push({ | |
type: SEQ_ENTRY, | |
length: top.length, | |
nodes: top.nodes, | |
index: top.index + 1, | |
results: top.results | |
}); | |
node = top.results[top.index + 1]; | |
continue recursion; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Version 4. CPS with a trampoline to prevent stack growth. Modular and iterative | |
// (but more expensive than loops). | |
// a trampoline computation is either done with a result or a thunk with more work to do: | |
// | |
// comp<a> = Done(a) | |
// | function() -> comp<a> | |
function Done(x) { | |
this.value = x; | |
} | |
// trampoline :: function(function(a ...) -> comp<b>) -> function(a ...) -> b | |
function trampoline(f) { | |
return function() { | |
for (var next = f.apply(this, arguments); typeof next === "function"; next = next()) | |
; | |
return next.value; | |
}; | |
} | |
// replace :: function(node, string, string) -> node | |
function replace(node, from, to) { | |
return (trampoline(replaceCPS))(node, from, to, function(x) { | |
return new Done(x) | |
}); | |
} | |
// replaceCPS :: function(node, string, string, function(node) -> comp<node>) -> comp<node> | |
function replaceCPS(node, from, to, k) { | |
switch (node.type) { | |
case IF: | |
return function() { | |
return replaceCPS(node.test, from, to, function(testNode) { | |
return function() { | |
return replaceCPS(node.then, from, to, function(thenNode) { | |
return function() { | |
return replaceCPS(node.else, from, to, function(elseNode) { | |
return k({ | |
type: IF, | |
test: testNode, | |
then: thenNode, | |
else: elseNode | |
}); | |
}); | |
}; | |
}); | |
}; | |
}); | |
}; | |
case ID: | |
return k({ | |
type: ID, | |
name: node.name.replace(from, to) | |
}); | |
case SEQ: | |
return mapCPS(node.nodes, function(node, k) { | |
return replaceCPS(node, from, to, k); | |
}, k); | |
// etc. | |
} | |
} | |
// mapCPS :: function([a], | |
// function(a, function(b) -> comp<c>) -> comp<c>, | |
// function([b]) -> comp<c>) | |
// -> comp<c> | |
function mapCPS(a, f, k) { | |
var i = 0, n = a.length, result = []; | |
return (function loop() { | |
if (i >= n) | |
return k(result); | |
return f(a[i], function(x) { | |
result[i] = x; | |
i++; | |
return loop; | |
}); | |
})(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment