title: Combine tornado gen.coroutine and joblib mem.cache decorators
body: <p>Imagine having a function, which handles a heavy computational job, that we wish to execute asynchronously in a Tornado application context. Moreover, we would like to lazily evaluate the function, by storing its results to the disk, and not rerunning the function twice for the same arguments.</p>
  
  <p>Without caching the result (memoization) one would do the following:</p>
  
  <pre><code>def complex_computation(arguments):
      ...
      return result
  
  @gen.coroutine
  def complex_computation_caller(arguments):
      ...
      result = complex_computation(arguments)
      raise gen.Return(result)
  </code></pre>
  
  <p>Assume to achieve function memoization, we choose <strong>Memory</strong> class from <strong>joblib</strong>. By simply decorating the function with <code>@mem.cache</code> the function can easily be memoized:</p>
  
  <pre><code>@mem.cache
  def complex_computation(arguments):
      ...
      return result
  </code></pre>
  
  <p>where <code>mem</code> can be something like <code>mem = Memory(cachedir=get_cache_dir())</code>.</p>
  
  <p>Now consider combining the two, where we execute the computationally complex function on an executor:</p>
  
  <pre><code>class TaskRunner(object):
      def __init__(self, loop=None, number_of_workers=1):
          self.executor = futures.ThreadPoolExecutor(number_of_workers)
          self.loop = loop or IOLoop.instance()
  
      @run_on_executor
      def run(self, func, *args, **kwargs):
          return func(*args, **kwargs)
  
  mem = Memory(cachedir=get_cache_dir())
  _runner = TaskRunner(1)
  
  @mem.cache
  def complex_computation(arguments):
      ...
      return result
  
  @gen.coroutine
  def complex_computation_caller(arguments):
      result = yield _runner.run(complex_computation, arguments)
      ...
      raise gen.Return(result)
  </code></pre>
  
  <p>So the first question is whether the aforementioned approach is technically correct?</p>
  
  <p>Now let's consider the following scenario:</p>
  
  <pre><code>@gen.coroutine
  def first_coroutine(arguments):
      ...
      result = yield second_coroutine(arguments)
      raise gen.Return(result)
  
  @gen.coroutine
  def second_coroutine(arguments):
      ...
      result = yield third_coroutine(arguments)
      raise gen.Return(result)
  </code></pre>
  
  <p>The second question is how one can memoize <code>second_coroutine</code>? Is it correct to do something like:</p>
  
  <pre><code>@gen.coroutine
  def first_coroutine(arguments):
      ...
      mem = Memory(cachedir=get_cache_dir())
      mem_second_coroutine = mem(second_coroutine)
      result = yield mem_second_coroutine(arguments)
      raise gen.Return(result)
  
  @gen.coroutine
  def second_coroutine(arguments):
      ...
      result = yield third_coroutine(arguments)
      raise gen.Return(result)
  </code></pre>
  
  <p><strong>[UPDATE I]</strong> <a href="https://stackoverflow.com/questions/32374238/caching-and-reusing-a-function-result-in-tornado">Caching and reusing a function result in Tornado</a> discusses using <code>functools.lru_cache</code> or <code>repoze.lru.lru_cache</code> as a solution for second question.</p>