Add @cached() decorator

Caches a function's return value based on its arguments.

        @cached(lambda a, b, date: [str(a), str(b), date.isoformat()])
        def generate_report(a, b, date):
            return frobnicate(
                slow_api_call(a, date),
                slow_api_call(b, date),
                munge(process_range(a, b)),
            )