What Is Include-Once?
In addition to an include facility, many languages offer the ability to be able to ‘include-once’. Where source files are variously dependent upon other files and upon each other, it’s easy to see how the same files could be included over and over. Being able to include-once would prevent such multiple inclusions of the same file by automatically only including each file the first time it’s included.
From the caller’s point of view, using include-once is syntactically very similar to using a regular include, e.g.:
In this example, the ‘usefulLib.js’ file (which presumably includes the implementation of the ‘usefulFn()’ function) would only be included if it hadn’t previously been included before (irrespective of whether the previous inclusion was performed using ‘include()’ or ‘includeOnce()’).
Even with ‘includeOnce()’ available, it would still be possible to force an include to take place. In the following code snippet, both ‘someCode1.js’ and ‘someCode2.js’ would be included twice:
include("someCode1.js"); include("someCode1.js"); includeOnce("someCode2.js"); include("someCode2.js"); // Only 'includeOnce()' checks for previous inclusion.
In order for the ‘include’ facility to be able to establish whether a file has previously been included, we need some means to uniquely identify files. Clearly, filename alone isn’t sufficient because different files could have the same filenames (as long as they’re within different directories). Although it might be tricky to think of a circumstance where a developer would have such an arrangement, it is possible in principle, therefore we need to make sure our ‘include-once’ feature won’t be wrong-footed by it.
Perhaps what we need is to use full pathnames (or even full URLs) to identify files. In [part 2 of this article series], better handling of file pathnames was introduced, but relative paths alone won’t cut it. If, for example, two files in different directories both include the same third file, they could refer to it using relative paths that won’t necessarily match exactly. In other words, although we’d have the same file being included twice, a casual glance at the relative paths alone might lead us to believe that two different files are being included.
Whenever an ‘include()’ or an ‘includeOnce()’ is requested, then, we’ll need to do a fair bit of processing on the supplied pathname to establish the full URL of the included file.
There are two parts to this process. Firstly, we need to take a look at the include file path we’ve been given and prefix it with whatever is required to make it into a full, absolute URL. At one extreme, this could involve no modification (in the case where we were supplied with a full URL anyway); at the other, we could be adding everything but the filename. Secondly, we need to make sure that the full URL we have doesn’t contain any ‘.’ or ‘..’ (i.e., ‘this directory’ or ‘parent directory’) parts, because this too could make two references to the same file look different from each other.
Although the process of converting (what could be) a relative include path into a full, absolute URL isn’t the proverbial rocket science, it is still a bit fiddly, and, requires more lines of code than it feels it deserves. I’ve implemented the process via two functions: ‘getRealUrl()’ that takes a full URL (i.e., one that begins with ‘http://’ and includes a domain) and parses out the ‘.’ and ‘..'; and ‘getAlreadyIncludedKey()’ that makes sure the include path is a full URL before calling upon ‘getRealUrl()’ to make sure there are no ‘.’ and ‘..’ parts.
Using a file’s full URL as a key, we can then make a note of every file that’s included (using either ‘include()’ or ‘includeOnce()’) and automatically refrain from including it as necessary if it has been included before (using ‘includeOnce()’).
The Final Include Code in Full
It’s taken quite an effort to produce as comprehensive an ‘include’ facility as a modern language ought to offer, but here’s the full implementation in all its glory (I toyed with the idea of only including in full those extra bits required for ‘include-once’ support, but thought it would be more convenient to work with if anyone was actually tempted to try using this new facility):
My own feeling about the final implementation is that it seems like an awful lot of code for adding a ‘mere’ include feature to a language, even if it is one that seems to be fairly comprehensive. I could have made the code shorter, of course, but only at the expense of making it either flakier or less flexible and powerful (unless we’re talking about just removing all unnecessary whitespace). If you’re prepared to put up with the limitations of the code presented in the first article, for example, then clearly it could be shorter, but only the fuller version presented here offers anything like the equivalent feature available in other languages.
Only time will tell whether I make use of my own new facility (and, in any event, it will need further testing and/or use before I’ll be satisfied that it is fully reliable with no lingering bugs), but at the very least, I’ve found that the journey has been interesting, and there are a few useful little functions and code snippets that, quite apart from forming part of the final ‘include’ implementation, may be useful in their own right. I can imagine, for example, that the algorithm used by the ‘getRealUrl()’ function for parsing the ‘.’ and ‘..’ bits from pathnames may in itself turn out to be useful one of these days.
Anyway, as ever, I’m open to your constructive comments and thoughts. And thanks for reading.