Last active
February 2, 2017 14:23
-
-
Save recmo/1a02121d39ee337fb81fc18e735a0d9e to your computer and use it in GitHub Desktop.
Async FizzBuzz — implemented using *only* callbacks
This file contains 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
// Async FizzBuzz -- implemented using only callbacks | |
// | |
// node --stack-size=100000 ./async-fizzbuzz.js | |
// | |
// Wrap process.stdout.write | |
const print = (message, callback) =>{ | |
process.stdout.write(message, callback) | |
} | |
// Scott numeral constructors | |
const Z = (callback)=>{callback((a,b)=>{a()})} | |
const S = (n, callback)=>{callback((a,b)=>{b(n)})} | |
// Numeral constants | |
const n0 = (a,b)=>{a()} | |
const n1 = (a,b)=>{b(n0)} | |
const n2 = (a,b)=>{b(n1)} | |
const n3 = (a,b)=>{b(n2)} | |
const n4 = (a,b)=>{b(n3)} | |
const n5 = (a,b)=>{b(n4)} | |
const n6 = (a,b)=>{b(n5)} | |
const n7 = (a,b)=>{b(n6)} | |
const n8 = (a,b)=>{b(n7)} | |
const n9 = (a,b)=>{b(n8)} | |
const n10 = (a,b)=>{b(n9)} | |
const if_zero = (n, then, else_)=>{n( | |
()=>{then()}, | |
(r)=>{else_()} | |
)} | |
const if_equal = (m, n, then, else_)=>{m( | |
()=>{if_zero(n, then, else_)}, | |
(mr)=>{n( | |
else_, | |
(nr)=>{if_equal(mr, nr, then, else_)} | |
)} | |
)} | |
const add = (m, n, callback)=>{m( | |
()=>{callback(n)}, | |
(mr)=>{S(n, (sn)=>{add(mr, sn, callback)})} | |
)} | |
const sub = (m, n, callback, error)=>{n( | |
()=>{callback(m)}, | |
(nr)=>{m( | |
error, | |
(mr)=>{sub(mr, nr, callback, error)} | |
)} | |
)} | |
const mul = (m, n, callback)=>{m( | |
()=>{Z(callback)}, | |
(mr)=>{mul(mr, n, (r)=>{add(r, n, callback)})} | |
)} | |
const divmod = (m, n, callback)=>{ | |
sub(m, n, | |
(mr)=>{divmod(mr, n, (q, r)=>{ | |
S(q, (sq)=>{callback(sq, r)}) | |
})}, | |
()=>{callback(n0, m)}) | |
} | |
const divides = (m, n, then, else_)=>{ | |
divmod(m, n, (q, r)=>{if_equal(r, n0, then, else_)}) | |
} | |
// Decimal output | |
const print_decimal = (n, callback, error)=>{ | |
if_equal(n, n0, ()=>{print("0", callback)}, ()=>{ | |
if_equal(n, n1, ()=>{print("1", callback)}, ()=>{ | |
if_equal(n, n2, ()=>{print("2", callback)}, ()=>{ | |
if_equal(n, n3, ()=>{print("3", callback)}, ()=>{ | |
if_equal(n, n4, ()=>{print("4", callback)}, ()=>{ | |
if_equal(n, n5, ()=>{print("5", callback)}, ()=>{ | |
if_equal(n, n6, ()=>{print("6", callback)}, ()=>{ | |
if_equal(n, n7, ()=>{print("7", callback)}, ()=>{ | |
if_equal(n, n8, ()=>{print("8", callback)}, ()=>{ | |
if_equal(n, n9, ()=>{print("9", callback)}, error | |
)})})})})})})})})}) // LISP flashbacks | |
} | |
const print_number = (n, callback)=>{ | |
divmod(n, n10, (q, r)=>{ | |
if_zero(q, ()=>{ | |
print_decimal(r, callback) | |
}, ()=>{ | |
print_number(q, ()=>{ | |
print_decimal(r, callback) | |
}) | |
}) | |
}) | |
} | |
// Loops | |
// for needs more callbacks. | |
// So far, we have only used anonymous functions. It would | |
// be a shame to stop that practice here. Therefore, I present | |
// you a fixed-point combinator in pure CPS: | |
const for_ = (start, end, func, callback)=>{ | |
((rec, n, end, func, callback)=>{ | |
rec(rec, n, end, func, callback) | |
})((rec, n, end, func, callback)=>{ | |
func(n, ()=>{ | |
if_equal(n, end, callback, ()=>{ | |
S(n, (sn)=>{ | |
rec(rec, sn, end, func, callback) | |
}) | |
}) | |
}) | |
}, start, end, func, callback) | |
} | |
// We are coming to the main show | |
const fizzbuzz_number = (n, callback)=>{mul(n3, n5, (n15)=>{ | |
divides(n, n15, ()=>{print("FizzBuzz\n", callback)}, ()=>{ | |
divides(n, n3, ()=>{print("Fizz\n", callback)}, ()=>{ | |
divides(n, n5, ()=>{print("Buzz\n", callback)}, ()=>{ | |
print_number(n, ()=>{print("\n", callback)}) | |
})})}) | |
})} | |
const fizzbuzz = (callback)=>{mul(n10, n10, (n100)=>{ | |
for_(n1, n100, fizzbuzz_number, callback) | |
})} | |
fizzbuzz(()=>{}) |
Updated the gist to use a fixed-point combinator and async process.stdout.write
. It is now 100% CPS.
In the new revision, the crash disappears, but it reappears if you do something complicated:
Replace the last line fizzbuzz(()=>{})
with:
mul(n10, n10, (n100)=>{mul(n100, n100, ()=>{})})
And nodejs crashes again. This hints that the crash is not related to IO.
Interestingly, if you replace the last line with these two lines:
fizzbuzz(()=>{})
mul(n10, n10, (n100)=>{mul(n100, n100, ()=>{})})
Then the program prints 1
and then crashes. So something about the second line happens during execution of the first. I suspect this is either the optimizer, or the two lines are speculatively executed in parallel.
I explain the anonymous recursion here: https://stackoverflow.com/questions/35918279/define-fix-point-combinator-in-continuation-passing-style/41975651#41975651
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
SegFaults nodejs/v8 while processing 91. Bugreports: nodejs v8