Last active
September 7, 2022 13:36
-
-
Save Biotronic/67bebfe97f17e73cc610d9bcd119adfb to your computer and use it in GitHub Desktop.
Head-mutable implementation for D
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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