I used to work as a Front End Engineer for a media company that runs some of the largest digital publications in Hungary. My team was responsible for the code of several websites. These share a large amount of code and run on the same central CMS.
The general styles, such as colors, sizes, typography, are also similar and they share some user interface components as well. In the codebase, these color, size and typography values are stored in variables — Sass maps to be exact.
We used Sass because it helped us maintain our CSS and create links between logically related code snippets.
When setting up these relations, we decided that we were going to separate global and local scopes. We put variables that were shared between publications in the global scope and extended or possibly overrode them in our local scope.
Keep in mind, I am not talking about actual, programming language scope here. What we did was create query functions, like
get-color() for example, which return values based on their arguments from a Sass map called
$colors. This variable was created by merging two variables,
$global-colors and that is how we basically store our different scopes.
Merging maps was the point where we ran into some trouble. Remember,
$local-colors stores the colors that are prevalent to the actual site only, while
$global-colors stores the shared values.
The problem was, we used multi-dimensional Sass maps.
Consider the simple example above. If we used the built-in Sass function map-merge to create $colors, we would get this:
The map-merge function simply adds the keys of the second argument to the first and in case the same key exists in the first argument, it overrides it, whatever it’s type or value is.
Our goal was to extend the first argument by adding the x-dark tone of the neutral color to the already existing neutral tones.
In other words, we wanted to merge a Sass map with another Sass map — all inside a Sass map.
While this sounds quite easy, there is one catch though: some of our variables are two-dimensional, some are three or four. We wanted to avoid manually writing merges for every variable type we have and wanted to create a one-size-fits-all kind of solution.
Exit map-merge; enter recursive functions
When we are talking about repeating a task n times, it usually takes us to either a for/while loop or a recursive function. This time, it is the latter.
Recursion in computer science is a method where the solution to a problem depends on solutions to smaller instances of the same problem…
When it comes to a function, that basically means that a function is going to call itself. Given that we want to merge lists inside of a list, this approach seems handy.
The entire function with examples can be found on GitHub, but I’ll also paste it here and go through each step.
We iterate over the child map, which is going to be the local lists in our case. We need to separate two main cases: when we want a standard map-merge and when we don’t.
On line 4, we check three things.
- Does the parent list have a key of the same name?
- Does the parent list’s key with the same name have the same type?
- Are they both maps?
We need to do a standard map-merge if the parent list doesn’t have the key with the same name, or it does, but they are of a different type or they are of the same type, but not maps.
The reason for this is extensibility. We wanted to be able to define anything on a global scope and override it locally.
In case we have two maps with matching key names, on line 8, we recursively call this same function we are defining, but this time, on the values themselves.
This solution allows us to do complex mergers, such as:
I hope you find this useful, let me know what you think, get in touch in case you have any questions or a better solution, approach to the problem!