I’ve been reading a-lot lately about Swift 3 future. Taking special interest in using Swift on the server and the work that is being done on that front.

Swift Trajectory 🚀

Swift has huge potential to become the dominant language for building everything from VR to Mobile to Server, to AI. Few key building blocks are still missing yet strong foundations have been laid. Among them and arguably the most important — functional support — which shipped with the language from day 1.

Functional Swift

Having functional support articulated so beautifully by the language constructs allows for some crazy hot functional implementation, one such example is the memoize function which was introduced in WWDC’14 Session 404 - Advanced Swift (discussion starts at 00:38).

In this post I hope to explore the nature of memoize and explain how it works, moving it from “Advanced” to “Basic” track for WWDC’17.

memoize

memoize's implementation at first glance looks like a Brainfuck inspired Hello World, but rest assured — it’s Swift. Pure Functional Swift 😼

memoize — Modus operandi

Simply put memoize is wrapping your function in a cache layer. The memoize wrapper intercepts calls you send to the function and attempts to reply with results from it’s internal cache, if it fails it calls the work function and records the result of the computation in memory, thus making next call a simple “fetch from memory”. This idea is one of oldest and simplest tricks of CS: Trading memory in exchange for CPU cycles. Redis, memcache, CDN’s and memoize are all different manifestations of it.

Figuratively speaking

let f be George R. R. Martin, then f(a Game of Thrones) takes 5 years.

let memoize be the librarian, then memoize(f(a Game of Thrones)) should take no more then 5 minutes: Mr. Librarian has to go to the right shelf, fetch the book, walk back to the counter and hand it over to you. An O(1) operation.

This makes memoize 525600 times faster then GRRM. 🙀

Step 0: Terminology

To explain memoize a few possible knowledge gaps need to be addressed.

Basic Terms

Generics — Using placeholders for Variable Types in functions. A hack invented for strongly typed programming languages, because all programmers should have the right to DRY.

— Using placeholders for Variable Types in functions. A hack invented for strongly typed programming languages, because all programmers should have the right to DRY. Closures —Fancy way of saying that you can create a new code block inside a function, and that this code block can use (reference) variables from the scope it was created in. OOP programmers take comfort in this terminology to help them break out of their Class/Object based worlds.

—Fancy way of saying that you can create a new code block inside a function, and that this code block can use (reference) variables from the scope it was created in. OOP programmers take comfort in this terminology to help them break out of their Class/Object based worlds. Higher Order Functions — Extremely fancy method to say that a function can accept another function as one of it’s parameters.

Step 1: Non recursive memoize

Behind the scenes Swift constantly uses a bag of epic syntactic sugar tricks, I will highlight the important aspects as we explore the various instructions. Starting with the definition of itos.

Line 12

I’m calling memoize here, using trailing closure syntax. The result of memoize (a closure) is stored into a parameter named itos .

Line 13

itos type is (Int) -> String . It was inferred using Swift’s (epic) type inference system.

Int is determined because I specify it in the closure definition: (n: Int)

is determined because I specify it in the closure definition: String is inferred because the closure returns a String (Line 14).

Line 1

memoize defines 2 type-placeholders: T, U.

memoize accepts 1 parameter: work , of type (T)->U . work is the original function that is being passed by the caller on Line 12.

Since we’ve already determined that itos type is (Int) -> String the placeholders of memoize for itos become T=Int, U=String .

Since memoize is a wrapper for work it must return a new function of the same type as the function it wraps, and indeed for itos case the return type of memoize becomes (Int) -> String

Line 4

Here be magic.

I’m returning a closure. This closure is the wrapper code — the heart of memoize . The closure is created and immediately returned. It will be stored by the variable named itos that is declared on Line 12.

For every memoize call, new caching dictionary (Line 2) and closure are created. Therefor you should only call memoize once per function.

The type of the closure that is being created is (Int) -> String .

Int is determined from it being used as input for work: work(x) (Line 6).

is determined from it being used as input for work: (Line 6). String is determined from work return type (Line 8).

Line 5

The reference to memo is a closure value capture — The closure will keep holding a strong reference to memo even after memoize returns, preventing ARC from claiming the Dictionary which would defeat the purpose of memoize , and would make at least one Chris very sad 🤕.

Step 1: Summary

itos is defined by a work closure that is passed to memoize which wraps it in a caching closure and returns the caching closure to the caller code. For each invocation of itos the caching closure checks to see if a cached result for the input parameter exists, if not it calls the work closure and caches the computation result.

Step 2: Recursive memoize

The gist of why this version of memoize is recursive is that the work closure is passed as a parameter to itself, which enables the work closure to make recursive calls to the memoize'd version of itself.

This qualifies for mind blowing epic win 🎆

Line 14

Using trailing closure syntax I’m passing a 2 parameter work closure to memoize. The result of memoize (a wrap closure) is stored into a parameter named factorial .

The call to factorial(x-1) from within the work closure body is referring to the parameter that is being passed to the closure, and is completely unrelated to the let factorial naming.

Line 15

let factorial type is (Int) -> Int

Input Int was inferred because Int Type was found to conform with the demands of:

1. Conform to Hashable protocol.

2. support == , * , and — operations.

was inferred because was found to conform with the demands of: Conform to protocol. support , , and operations. Return Int was inferred because no type conversion is performed before it is being returned.

Line 1

The recursion supporting version of memoize also uses 2 type-placeholders: T, U.

Since we’ve already determined that factorial type is (Int) -> Int the placeholders of memoize for factorial become T=Int, U=Int .

memoize accepts 1 input parameter, a closure, of type ((T)->U, T) -> U . work is the closure that is being passed by the caller on Line 15.

The return type of the memoize remains (T) -> U , a wrapper closure. Specialised for factorial this becomes (Int) -> Int.

Line 4

Here I define a nested function. Nested functions are just named closures. Using nested function is useful here because it carries a (T) -> U type, required for Swift type enforcement to pass the checks on line 6.

Line 6

Caution unicorns ahead 🦄

work is called to do the actual computation. work is a closure that I defined on line 15, it accepts 2 parameters:

wrap which is the recursive call (read: the magic ) for function from within the work closure. The work closure is able to call wrap again, and since wrap references work the result is a recursive call to work with a caching layer in between — Exactly like the non recursive version of memoize behaves, only with recursion built-in ✌️.

which is the recursive call (read: ) for function from within the work closure. The work closure is able to call again, and since references the result is a recursive call to with a caching layer in between — Exactly like the non recursive version of behaves, only with recursion built-in ✌️. x is the value caller code, as well as subsequent recursive calls pass as the input parameter to compute the result.

Step 2: Summary

factorial is a nested wrap function generated by the recursive version of memoize. The wrap function holds a reference to the work closure that it calls internally, passing a reference to itself.

Recursive memoize simplified

A wrap function holds a reference to the original function and for each iteration of the recursion the wrap function passes a reference to itself so that the original function will call it again, thus enable the recursion.

For example:

Think of A holding a reference to B.

A is asked to compute the value a by the caller code. A makes a call to B , passing:

a — the value it was requested to compute the result of, and

A — a reference to itself. B is left to decide how it handles the computation request, it can return a result for a or make another call to B which will cycle through back to A , i.e. deepen the recursion stack by 1.