Reputation: 6531
I'm trying to keep my front-end stack as functionally pure and immutable as possible and its done wonders for this project.
It is said all algorithms that can be implemented mutably can be implemented immutably, so in javascript how would I implement an immutable function memoizer? Because in order to return through a different path in the function some state inside or outside the function would have to change.
With the following function, how you can have an immutable memoization in javascript?
function once(fn){
let returnValue;
let canRun = true;
return function runOnce(){
if(canRun) {
returnValue = fn.apply(this, arguments);
canRun = false;
}
return returnValue;
}
}
var processonce = once(process);
processonce(); //process
processonce(); //
Upvotes: 4
Views: 952
Reputation: 1579
I propose a different io aproach, using generators can we say it's pure ?
function* _cache() {
const value = yield null
while (true) yield value
}
const cache = _cache()
const heavyComputation = i => {
console.log(`uh oh that's some heavy computations here`)
return i++
}
cache.next().value
cache.next(heavyComputation(1))
cache.next().value
cache.next().value
you can then use it to cache a value at runtime
function* _cache() {
const value = yield null
while (true) yield value
}
const cache = _cache()
const loadMongo = uri => void console.log(`fully loaded ${uri}`)
function end(loadedMongo) {
console.log('handler called')
}
function handler() {
const mongoUri = 'salutos speculos'
const loaded = cache.next().value
if (loaded === null) {
cache.next(loadMongo(mongoUri))
end(cache.next().value)
} else end(loaded)
}
handler()
handler()
handler()
handler()
handler()
Upvotes: 0
Reputation: 626
I was also curious about this question. I agree with @zch - passing the immutable state around would work (the initial call will initialize recursion with empty state).
So, I did my homework with implementing Fibonacci function: remember, we need it at least twice for n-1
and for n-2
. When we call fib(n-2)
- it is already memoized.
Here is my implementation:
const resultCombination = cache => result => (getResult = true) => getResult ? result : cache
const cacheResult = ({resultCombination}) => result => n => resultCombination({...result(false), [n]: result()})(result())
const memoize = ({resultCombination, cacheResult}) => cache => f => n => cache.hasOwnProperty(n)
? resultCombination(cache)(cache[n])
: cacheResult({resultCombination})(f(cache)(n))(n)
const fib2 = ({resultCombination, cacheResult, memoize}) => f1Result => f => n => resultCombination(f1Result(false))(f1Result() + memoize({resultCombination, cacheResult})(f1Result(false))(f)(n - 2)())
const fib = ({resultCombination, cacheResult, memoize, fib2}) => cache => n => n === 1 || n === 2
? resultCombination(cache)(1)
: fib2({resultCombination, cacheResult, memoize})(memoize({resultCombination, cacheResult})(cache)(fib({resultCombination, cacheResult, memoize, fib2}))(n - 1))(fib({resultCombination, cacheResult, memoize, fib2}))(n)
console.log('THE RESULT: ' + fib({
resultCombination,
cacheResult: ({resultCombination}) => result => n => {
console.log(`Caching result: f(${n})=${result()}`)
return cacheResult({resultCombination})(result)(n)
},
memoize: ({resultCombination, cacheResult}) => cache => f => n => {
console.log(cache.hasOwnProperty(n) ? `Cache hit for n=${n}` : `Calculating value for f(${n})`)
return memoize({resultCombination, cacheResult})(cache)(f)(n)
},
fib2
})({})(8)(true))
// Calculating value for f(7)
// Calculating value for f(6)
// Calculating value for f(5)
// Calculating value for f(4)
// Calculating value for f(3)
// Calculating value for f(2)
// Caching result: f(2)=1
// Calculating value for f(1)
// Caching result: f(1)=1
// Caching result: f(3)=2
// Cache hit for n=2
// Caching result: f(4)=3
// Cache hit for n=3
// Caching result: f(5)=5
// Cache hit for n=4
// Caching result: f(6)=8
// Cache hit for n=5
// Caching result: f(7)=13
// Cache hit for n=6
// THE RESULT: 21
For better understanding, what's going on - the cacheResult
and memoize
functions are injected with logging wrappers. As you can see, all functions are pure, taking only one parameter (except dependency injection).
Be sure, that resultCombination(cache)(result)
- is just replacement of {cache, result}
data structure.
P.S. I'm not a Haskell nerd (even don't know the Haskell or Lisp syntax at all), but I'm passionate about functional programming
Upvotes: 6