PragDave Fibonacci solution

You are actually not identifying which aspects of the code you are having trouble with.

  • The code starts with CachedFib.fib/1 which simply invokes Cache.run/1 - supplying it’s own anonymous function for calculating any desired value with CachedFib.cached_fib/2. What could be a bit more clear here is that cache is a process identifier (PID).

  • CachedFib.cached_fib/2 tries to look up the value but also supplies an alternate function to calculate the requested value (by accessing the two “previous” values from the “cache process” cache and summing them). Supplying this “backup strategy” function feels weird but is a concession to the agent - agents only hold state - not logic. So any logic has to be supplied externally. The point here is that Cache simply caches key value pairs - how these key value pairs are generated is up to the client. (Ultimately agents aren’t that useful but in Elixir learning resources they are often used as a first exposure to processes).

  • Meanwhile Cache.run/1 simply starts up the agent and stores the initial values for n = 0 and n = 1 in the agent (to be consistent this initial value should really come from CacheFib.fib/1 as CacheFib.cache_fib/2 also supplies the logic for calculating key/value pairs). The second expression hands the passed anonymous function body the agent’s PID (granting CachedFib.cached_fib/2 access to the agent’s state so that it can be accessed and manipulated with the remaining Cache functions) binding the final result of the body function. The third expression terminates the agent process. The fourth and final expression simply returns the result generated by the second expression.

  • The remaining Cache functions concern themselves with looking up previously calculated values or adding new ones as generated by the if_not_found function - i.e.:

fn -> cached_fib(n-2, cache) + cached_fib(n-1, cache) end
  • Cache.set/3 creates a new “state” for the agent by taking the map already stored in the agent and adding {n,val} to it. Note the definition of Agent.get_and_update/3 - Cache.set/3 will return val which is only part of the {n,val} pair - i.e. the calculated value.

  • Cache.complete_if_not_found/4 uses pattern matching across two function clauses (defining one function). The first clause (the first argument value is nil) is invoked when a requested value wasn’t found in the map (stored as state in the agent); the supplied if_not_found function is invoked to generate the desired value and the result is pipelined into Cache.set/3 to update the agent’s state. Note that calculated result will be returned by Cache.set/3. The second complete_if_not_found/4 function clause simply returns the value it was given (from the map stored as the agent state).

  • Cache.lookup/3 simply tries to look up the requested value. The result drives what happens with Cache.complete_if_not_found/4 - it either simply returns the looked up value or generates the required value with the supplied if_not_found function.

1 Like