# Dependency resolution and mutual recursion Recursion can be tricky to get your mind around, but satisfying once you do. I recently stumbled upon a problem for which a kind of *mutual* recursion turned out to be the simplest solution. It took me a moment to figure out (because I found it even more brain-bendy than "normal" recursion), but I was glad once I did. I hope you enjoy it as much as I do. ## The (simpler) problem Let's start with a simple version of the problem, for which (normal) recursion is sufficient. Suppose we're trying to write a dependency resolution algorithm. My package (or more generally, *artifact*) depends on specific versions of some others — which, in turn, depend on others, etc. We want to collect the set of ALL transitive dependenices (the "transitive closure"). A simple way to do this is with a depth-first traversal. The skeleton would look like this: ``` def resolve(Artifact a): Set[Artifact] { Set deps // Collect all deps here for (dep in a.deps) { deps += resolve(dep) } return deps } ``` Of course, this doesn't handle error cases. For example, we should fail if there's a conflict (e.g., I depend on Foo 1.1, but I also depend on something else that requires Foo 1.2). We can accomplish that by keeping track of accumulated dependencies instead of returning it as a set: ``` def resolve(Artifact a, Set[Artifact] acc) { if (a.name in acc and a.version doesn't match) throw DependencyResolutionError else acc += a for (dep in a.deps) { resolve(dep, acc) } } ``` We can also add a helper method for the user: ``` def resolve(Artifact a): Set[Artifact] { Set acc = Set.empty resolve(a, acc) return acc } ``` ## The (more fun) problem In the simple version, we depend on only one version of each artifact. But what if multiple versions are allowed? E.g., what if I can depend on `Foo 1.x`? Suppose my artifact has three dependencies: `Foo 1.x`, `Bar 1.x`, `Baz 1.x`. And suppose there are three such version of `Foo`, four of `Bar`, and five of `Baz`. Then I potentially have to try 3 * 4 * 5 = 60 combinations to see if there's a good completion. One way to do this would be to iterate over all 60 such tuples (x, y, z) and do a depth-first traversal for each. It may be tempting to try it like this: ``` def resolve(Artifact a, Set[Artifact] acc) { if (a.name in acc and a.version doesn't match) throw DependencyResolutionError else acc += a // Generate all tuples ((x1, y1, z1), (x1, y1, z2), ...) // Perhaps do it in a lazy way, to save time and space. List[List[Artifacts]] depsList = ... for (deps in depsList) { for (dep in deps) { resolve(dep, acc) } } } ``` But this gets ugly quickly. Not only do we need to catch exceptions, but we need to unwind `acc` to recover from those failed searches. Moreover, we shouldn't just blindly continue down the list of tuples. If resolution of `x1` throws while resolving `(x1, y1, z1)`, we shouldn't continue trying `(x1, y1, z2)` etc. We need something smarter. An alternative is to use recursion again. Instead of explicitly enumerating all tuples, we call a function to handle (all possible choices for) the first element of the tuple, and it is responsible for invoking the function on the second element, etc. ``` def resolve(a: Artifact, acc: Set[Artifact]): Set[Artifact] { if (a.name in acc and a.version doesn't match) throw DependencyResolutionError else acc += a return a + walkChildren(a.deps, 0, acc + a) } def walkChildren(children: List[List[Artifact]], i: Int, acc: Set[Artifact]): Set[Artifact] { if (i >= children.length) return Set.empty for (child in children[i]) { res = resolve(child, acc) return res + walkChildren(children, i+1, acc + res) } } ```