Skip to content

Instantly share code, notes, and snippets.

@Biotronic
Last active September 7, 2022 13:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Biotronic/67bebfe97f17e73cc610d9bcd119adfb to your computer and use it in GitHub Desktop.
Save Biotronic/67bebfe97f17e73cc610d9bcd119adfb to your computer and use it in GitHub Desktop.
Head-mutable implementation for D
import std.traits;
import std.typecons : rebindable, Rebindable;
alias HeadMutable(T) = typeof(T.init.headMutable());
alias HeadMutable(T, ConstSource) = HeadMutable!(CopyTypeQualifiers!(ConstSource, T));
auto headMutable(T)(T value) {
static if (isPointer!T) {
// T is a pointer, and decays naturally.
return value;
} else static if (isDynamicArray!T) {
// T is a dynamic array, and decays naturally.
return value;
} else static if (!hasAliasing!(Unqual!T)) {
// T is a POD datatype - either a built-in type, or a struct with only POD members.
return cast(Unqual!T)value;
} else static if (isStaticArray!T && is(HeadMutable!(ElementType!T) == ElementType!(Unqual!T))) {
// T is a static array, and can safely be cast to Unqual!T.
// Can't cast directly to HeadMutable!(ElementType!T)[N], since ElementType!T.headMutable()
// may change the layout of the type.
return cast(Unqual!T)value;
} else static if (is(T == class)) {
// Classes are reference types, so only the reference to it may be made head-mutable.
return rebindable(value);
} else static if (isAssociativeArray!T) {
// AAs are reference types, so only the reference to it may be made head-mutable.
return rebindable(value);
} else {
static assert(false, "Type `"~T.stringof~"` cannot be made head-mutable.");
}
}
unittest
{
// Regular types:
static assert(is(HeadMutable!(int) == int));
static assert(is(HeadMutable!(int*) == int*));
static assert(is(HeadMutable!(int[]) == int[]));
static assert(is(HeadMutable!(int[10]) == int[10]));
static assert(is(HeadMutable!(int*[10]) == int*[10]));
static assert(is(HeadMutable!(int[int]) == Rebindable!(int[int])));
static assert(is(HeadMutable!(const int) == int));
static assert(is(HeadMutable!(const int*) == const(int)*));
static assert(is(HeadMutable!(const int[]) == const(int)[]));
static assert(is(HeadMutable!(const int[10]) == int[10]));
static assert(is(HeadMutable!(const int*[10]) == const(int)*[10]));
static assert(is(HeadMutable!(const int[int]) == Rebindable!(const(int[int]))));
static assert(is(HeadMutable!(immutable int) == int));
static assert(is(HeadMutable!(immutable int*) == immutable(int)*));
static assert(is(HeadMutable!(immutable int[]) == immutable(int)[]));
static assert(is(HeadMutable!(immutable int[10]) == int[10]));
static assert(is(HeadMutable!(immutable int*[10]) == immutable(int)*[10]));
static assert(is(HeadMutable!(immutable int[int]) == Rebindable!(immutable(int[int]))));
// Class:
class C1 { }
static assert(is(HeadMutable!C1 == C1));
static assert(is(HeadMutable!(const C1) == Rebindable!(const C1)));
static assert(is(HeadMutable!(immutable C1) == Rebindable!(immutable C1)));
// Struct without aliasing:
static struct S1 {
int n;
immutable(int)[] arr;
}
static assert(is(HeadMutable!S1 == S1));
static assert(is(HeadMutable!(const S1) == S1));
static assert(is(HeadMutable!(immutable S1) == S1));
// Struct with aliasing has no head-mutable analog:
static struct S2 {
int[] arr;
}
// Should be possible to create head-mutable version of all-mutable struct.
//static assert(is(HeadMutable!S2));
static assert(!is(HeadMutable!(const S2)));
static assert(!is(HeadMutable!(immutable S2)));
// Struct with aliasing, with headMutable hook:
static struct S3 {
int[] arr;
S3 headMutable(this T)() {
return S3(arr.dup);
}
}
static assert(is(HeadMutable!S3 == S3));
static assert(is(HeadMutable!(const S3) == S3));
static assert(is(HeadMutable!(immutable S3) == S3));
// Templated struct with aliasing, with headMutable hook
struct S4(T) {
T[] arr;
auto headMutable(this This)() const {
alias HeadMutableS4 = S4!(CopyTypeQualifiers!(This, T));
return HeadMutableS4(arr.dup);
}
}
static assert(is(HeadMutable!(S4!int) == S4!int));
static assert(is(HeadMutable!(const S4!int) == S4!(const int)));
static assert(is(HeadMutable!(immutable S4!int) == S4!(immutable int)));
}
unittest
{
{
// No copying of values.
const(int)[] a = [1,2,3];
auto b = a.headMutable();
assert(a is b);
}
{
// No copying of values.
immutable(int)[] a = [1,2,3];
auto b = a.headMutable();
assert(a is b);
}
{
// No copying of values.
const(int[]) a = [1,2,3];
auto b = a.headMutable();
assert(a is b);
}
{
// No copying of values.
immutable(int[]) a = [1,2,3];
auto b = a.headMutable();
assert(a is b);
}
{
// No copying of values unless the headMutable() member function says so.
struct S4(T) {
T[] arr;
auto headMutable(this This)() const {
return S4!(CopyTypeQualifiers!(This, T))(arr);
}
}
const S4!int a = S4!int([1,2,3]);
auto b = a.headMutable();
assert(a.arr is b.arr);
}
{
// No copying of values unless the headMutable() member function says so.
struct S5(T) {
T[] arr;
auto headMutable(this This)() const {
return S5!(CopyTypeQualifiers!(This, T))(arr);
}
}
immutable S5!int a = S5!int([1,2,3]);
auto b = a.headMutable();
assert(a.arr is b.arr);
}
}
import std.range;
// Note that we check not if R is a range, but if HeadMutable!R is
auto map(alias Fn, R)(R range) if (isInputRange!(HeadMutable!R)) {
// Using HeadMutable!R and range.headMutable() here.
// This is basically the extent to which code that uses head-mutable data types will need to change.
return MapResult!(Fn, HeadMutable!R)(range.headMutable());
}
struct MapResult(alias Fn, R) {
R range;
this(R _range) {
range = _range;
}
void popFront() {
range.popFront();
}
@property
auto ref front() {
return Fn(range.front);
}
@property
bool empty() {
return range.empty;
}
static if (isBidirectionalRange!R) {
@property
auto ref back() {
return Fn(range.back);
}
void popBack() {
range.popBack();
}
}
static if (hasLength!R) {
@property
auto length() {
return range.length;
}
alias opDollar = length;
}
static if (isRandomAccessRange!R) {
auto ref opIndex(size_t idx) {
return Fn(range[idx]);
}
}
static if (isForwardRange!R) {
@property
auto save() {
return MapResult(range.save);
}
}
static if (hasSlicing!R) {
auto opSlice(size_t from, size_t to) {
return MapResult(range[from..to]);
}
}
// All the above is as you would normally write it.
// We also need to implement headMutable().
// Generally, headMutable() will look very much like this - instantiate the same
// type template that defines typeof(this), use HeadMutable!(T, ConstSource) to make
// the right parts const or immutable, and call headMutable() on fields as we pass
// them to the head-mutable type.
auto headMutable(this This)() const {
alias HeadMutableMapResult = MapResult!(Fn, HeadMutable!(R, This));
return HeadMutableMapResult(range.headMutable());
}
}
auto equal(R1, R2)(R1 r1, R2 r2) if (isInputRange!(HeadMutable!R1) && isInputRange!(HeadMutable!R2)) {
// Need to get head-mutable version of the parameters to iterate over them.
auto _r1 = r1.headMutable();
auto _r2 = r2.headMutable();
while (!_r1.empty && !_r2.empty) {
if (_r1.front != _r2.front) return false;
_r1.popFront();
_r2.popFront();
}
return _r1.empty && _r2.empty;
}
unittest {
// User code does not have to use headMutable at all:
const arr = [1,2,3];
const squares = arr.map!(a => a*a);
const squaresPlusTwo = squares.map!(a => a+2);
assert(equal(squaresPlusTwo, [3, 6, 11]));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment