Yan Cui
I help clients go faster for less using serverless technologies.
This article is brought to you by
Don’t reinvent the patterns. Catalyst gives you consistent APIs for messaging, data, and workflow with key microservice patterns like circuit-breakers and retries for free.
I touched on the topic of memoization in the past in relation to doing aspect-oriented programming with PostSharp, however, with functional languages like F#, Haskell or Erlang there is no such frameworks (although PostSharp should still work with F# to some extent) to help you.
That’s not to say that you can’t do AOP in a functional language though, in fact, here’s a simple implementation of the aforementioned memoizer in F# as a higher-order function:
Here’s a FSI session that shows the memoize function in action:
Note that the memoized version of the original function f took a second to execute the first time around but then subsequent calls didn’t take anytime.
Parting thoughts…
The implementation I’ve shown here is a very basic and only works for functions that takes in a single parameter, if you can think of an elegant way to make it support functions with different number of inputs please feel free to contact me as I’m very interested to hear about your thoughts.
Also, this implementation won’t work with recursive functions because when the function recurs it will be calling the non-memoized version of the function, it’ll require some special handling to make a recursive function ‘memoizable’.
In general, I don’t feel AOP is as well suited to functional programming as it is to object oriented programming, but there’s still a pocket of use cases where AOP can be beneficial. Memoization is one of them, so is tracing and input validation, all of which is possible through the use of higher-order functions. A word of warning though, If you’re familiar with frameworks such as PostSharp, the AOP experience you’re going to get through higher-order functions is not going to be as unobtrusive as you’re used to.
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.
Your memoize doesn’t work for recursive functions: when f input is called, recursive calls to f will not call the memoize function. You need to combine this technique with functions that are mutable:
let mutable fib = fun (n:int) ->0; // placeholder for recursive lambda term
fib if (n<3) then 1 else fib(n-1) + fib(n-2);
fib <- memoize(fib);
Now the recursive calls to fib will also call the memoized fib.
In my opinion all let-bindings should be mutable by default, if you really want to treat functions as "first class citizens".
Chuckov – this is an old post and I didn’t wanna go into how to make it work with recursive functions at the time, if you wanna see how to do that WITHOUT usable mutable state, check this out: https://github.com/theburningmonk/IntroductionToAOP/blob/master/AopDemo/AopDemo.Functional/Demo.fsx
Functions as a “first class citizen” has nothing to do with mutability, it just means functions can be passed around as arguments, and returned just like any other value in the language. And if you make all let-binding mutable in F# and take away one of the fundamental benefits of a FP-first language then you might as well program in another language, you’ll just be fighting the language at that point.