Thoughts on versioning
Andrei Alexandrescu
SeeWebsiteForEmail at erdani.org
Tue Oct 26 01:19:29 UTC 2021
Versioning Phobos would free us from maintaining backward compatibility
with a variety of decisions that did not withstand the test of time:
- autodecoding
- liberal use of the GC
- antediluvian run-time support library API (Object, Typeinfo, ...)
- liberal throwing of exceptions
- many others
Goals of library versioning:
- avoid copy-and-paste code duplication across versions
- support mixed use of versions for incremental migration
- allow library writers to structure code in a versioning-friendly manner
Non-goals:
- migrate code that was not written for versioning in a black-box manner
(i.e. without touching it)
- support arbitrarily radical API changes with no effort
To open the discussion, here's a simple example: say we want to port the
mismatch function in std.algorithm.comparison to a future std2 version
that removes autodecoding. (I'll use the two-parameters version for
simplicity.)
Tuple!(Range1, Range2)
mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2)
if (isInputRange!(Range1) && isInputRange!(Range2))
{
for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
{
if (!binaryFun!(pred)(r1.front, r2.front)) break;
}
return tuple(r1, r2);
}
This same source code would work with or without autodecoding. It all
depends on how the primitives front and popFront are implemented. If
pasted within an std2 version without autodecoding, it would just work.
Of course, we don't want to paste it twice, so the challenge is to have
the source code in a single place and use it both in std, with
auto-decoding, and std2, without autodecoding.
One naive solution would be to simply instruct mismatch to look up
front, popFront etc. in the same namespace in which it's called, much
like a C-style macro. This is called "dynamic lookup" and its use is
largely discredited at least in statically-typed languages.
So there's a need to parameterize mismatch with the namespace in which
lookup is to be done. For example:
// In std/algorithm/comparison.d:
template mismatch(alias the_std)
{
import the_std.typecons : Tuple, tuple;
import the_std.range.primitives : isInputRange, empty, front, popFront;
Tuple!(Range1, Range2)
mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2)
if (isInputRange!(Range1) && isInputRange!(Range2))
{
for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
{
if (!binaryFun!(pred)(r1.front, r2.front)) break;
}
return tuple(r1, r2);
}
}
Then:
// In std/algorithm/comparison.d:
alias mismatch = mismatch!std;
// In std2/algorithm/comparison.d:
alias mismatch = mismatch!std2;
Pros:
- no duplication of implementation
- freedom to pull an old name in a new version, or simply redefine it
Cons:
- one line of scaffolding per name introduced; can't do bulk without a
bit of support compile-time reflectioncode
- does not support easy migration of directory structure - what if we
want to reorganized the modules such that Tuple is no longer in
std2.typecons?
- does not work within the current language
About the last point: currently the language does allow passing a
package name as an alias, but trying to import the_std.typecons ignores
any alias definition and opens the hardcoded path "the_std/typcons.d".
This would need to be changed. If breaking changes are to be allowed we
need to introduce a new syntax such as:
import alias(the_std).typecons : Tuple, tuple;
There are many other aspects to discuss, but I'll keep this short so the
discussion doesn't meander too much.
More information about the Digitalmars-d
mailing list