Replace Option with Nullable

This commit is contained in:
Eugen Wissner 2021-03-27 10:28:49 +01:00
parent 0fcc83d00e
commit 92284c8541
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
6 changed files with 48 additions and 438 deletions

View File

@ -21,11 +21,11 @@
module tanya.algorithm.iteration; module tanya.algorithm.iteration;
import std.algorithm.comparison; import std.algorithm.comparison;
import std.typecons;
import tanya.memory.lifetime; import tanya.memory.lifetime;
import tanya.meta.trait; import tanya.meta.trait;
import tanya.meta.transform; import tanya.meta.transform;
import tanya.range; import tanya.range;
import tanya.typecons;
// These predicates are used to help preserve `const` and `inout` for // These predicates are used to help preserve `const` and `inout` for
// ranges built on other ranges. // ranges built on other ranges.
@ -570,7 +570,7 @@ if (isBidirectionalRange!Range)
private struct SingletonByValue(E) private struct SingletonByValue(E)
{ {
private Option!E element; private Nullable!E element;
@disable this(); @disable this();
@ -581,9 +581,9 @@ private struct SingletonByValue(E)
} }
private this(U)(ref U element) private this(U)(ref U element)
if (is(Unqual!U == Option!(Unqual!E)) || is(Unqual!U == Option!(const E))) if (is(Unqual!U == Nullable!(Unqual!E)) || is(Unqual!U == Nullable!(const E)))
{ {
if (!element.isNothing) if (!element.isNull)
{ {
this.element = element.get; this.element = element.get;
} }
@ -600,19 +600,19 @@ private struct SingletonByValue(E)
void popFront() void popFront()
in (!empty) in (!empty)
{ {
this.element.reset(); this.element.nullify();
} }
alias popBack = popFront; alias popBack = popFront;
@property bool empty() const @property bool empty() const
{ {
return this.element.isNothing; return this.element.isNull;
} }
@property size_t length() const @property size_t length() const
{ {
return !this.element.isNothing; return !this.element.isNull;
} }
auto save() auto save()
@ -620,11 +620,6 @@ private struct SingletonByValue(E)
return SingletonByValue!E(this.element); return SingletonByValue!E(this.element);
} }
auto save() const
{
return SingletonByValue!(const E)(this.element);
}
ref inout(E) opIndex(size_t i) inout ref inout(E) opIndex(size_t i) inout
in (!empty) in (!empty)
in (i == 0) in (i == 0)
@ -675,11 +670,6 @@ private struct SingletonByRef(E)
return typeof(this)(*this.element); return typeof(this)(*this.element);
} }
auto save() const return
{
return SingletonByRef!(const E)(*this.element);
}
ref inout(E) opIndex(size_t i) inout return ref inout(E) opIndex(size_t i) inout return
in (!empty) in (!empty)
in (i == 0) in (i == 0)

View File

@ -14,6 +14,7 @@
*/ */
module tanya.math.random; module tanya.math.random;
import std.typecons;
import tanya.memory.allocator; import tanya.memory.allocator;
import tanya.typecons; import tanya.typecons;
@ -90,8 +91,8 @@ abstract class EntropySource
* Postcondition: Returned length is less than or equal to * Postcondition: Returned length is less than or equal to
* $(D_PARAM output) length. * $(D_PARAM output) length.
*/ */
Option!ubyte poll(out ubyte[maxGather] output) @nogc Nullable!ubyte poll(out ubyte[maxGather] output) @nogc
out (length; length.isNothing || length.get <= maxGather); out (length; length.isNull || length.get <= maxGather);
} }
version (CRuntime_Bionic) version (CRuntime_Bionic)
@ -151,12 +152,12 @@ version (linux)
* Returns: Number of bytes that were copied to the $(D_PARAM output) * Returns: Number of bytes that were copied to the $(D_PARAM output)
* or nothing on error. * or nothing on error.
*/ */
override Option!ubyte poll(out ubyte[maxGather] output) @nogc nothrow override Nullable!ubyte poll(out ubyte[maxGather] output) @nogc nothrow
{ {
// int getrandom(void *buf, size_t buflen, unsigned int flags); // int getrandom(void *buf, size_t buflen, unsigned int flags);
import mir.linux._asm.unistd : NR_getrandom; import mir.linux._asm.unistd : NR_getrandom;
auto length = syscall(NR_getrandom, output.ptr, output.length, 0); auto length = syscall(NR_getrandom, output.ptr, output.length, 0);
Option!ubyte ret; Nullable!ubyte ret;
if (length >= 0) if (length >= 0)
{ {
@ -202,11 +203,11 @@ else version (SecureARC4Random)
* Returns: Number of bytes that were copied to the $(D_PARAM output) * Returns: Number of bytes that were copied to the $(D_PARAM output)
* or nothing on error. * or nothing on error.
*/ */
override Option!ubyte poll(out ubyte[maxGather] output) override Nullable!ubyte poll(out ubyte[maxGather] output)
@nogc nothrow @safe @nogc nothrow @safe
{ {
(() @trusted => arc4random_buf(output.ptr, output.length))(); (() @trusted => arc4random_buf(output.ptr, output.length))();
return Option!ubyte(cast(ubyte) (output.length)); return Nullable!ubyte(cast(ubyte) (output.length));
} }
} }
} }
@ -310,10 +311,10 @@ else version (Windows)
* Returns: Number of bytes that were copied to the $(D_PARAM output) * Returns: Number of bytes that were copied to the $(D_PARAM output)
* or nothing on error. * or nothing on error.
*/ */
override Option!ubyte poll(out ubyte[maxGather] output) override Nullable!ubyte poll(out ubyte[maxGather] output)
@nogc nothrow @safe @nogc nothrow @safe
{ {
Option!ubyte ret; Nullable!ubyte ret;
assert(hProvider > 0, "hProvider not properly initialized"); assert(hProvider > 0, "hProvider not properly initialized");
if ((() @trusted => CryptGenRandom(hProvider, output.length, cast(PBYTE) output.ptr))()) if ((() @trusted => CryptGenRandom(hProvider, output.length, cast(PBYTE) output.ptr))())

View File

@ -15,6 +15,7 @@
module tanya.net.ip; module tanya.net.ip;
import std.algorithm.comparison; import std.algorithm.comparison;
import std.typecons;
import tanya.algorithm.iteration; import tanya.algorithm.iteration;
import tanya.algorithm.mutation; import tanya.algorithm.mutation;
import tanya.container.string; import tanya.container.string;
@ -242,7 +243,7 @@ struct Address4
* *
* Returns: $(D_PARAM output). * Returns: $(D_PARAM output).
*/ */
OR toString(OR)(OR output) const @nogc nothrow pure @safe OR toString(OR)(OR output) const
if (isOutputRange!(OR, const(char)[])) if (isOutputRange!(OR, const(char)[]))
{ {
const octets = (() @trusted => (cast(ubyte*) &this.address)[0 .. 4])(); const octets = (() @trusted => (cast(ubyte*) &this.address)[0 .. 4])();
@ -324,10 +325,10 @@ struct Address4
* R = Input range type. * R = Input range type.
* range = Stringish range containing the address. * range = Stringish range containing the address.
* *
* Returns: $(D_PSYMBOL Option) containing the address if the parsing was * Returns: $(D_PSYMBOL Nullable) containing the address if the parsing was
* successful, or nothing otherwise. * successful, or nothing otherwise.
*/ */
Option!Address4 address4(R)(R range) Nullable!Address4 address4(R)(R range)
if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R) if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R)
{ {
Address4 result; Address4 result;
@ -370,10 +371,10 @@ if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R)
* R = Input range type. * R = Input range type.
* range = $(D_KEYWORD ubyte) range containing the address. * range = $(D_KEYWORD ubyte) range containing the address.
* *
* Returns: $(D_PSYMBOL Option) containing the address if the $(D_PARAM range) * Returns: $(D_PSYMBOL Nullable) containing the address if the $(D_PARAM range)
* contains exactly 4 bytes, or nothing otherwise. * contains exactly 4 bytes, or nothing otherwise.
*/ */
Option!Address4 address4(R)(R range) Nullable!Address4 address4(R)(R range)
if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte)) if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte))
{ {
Address4 result; Address4 result;
@ -407,11 +408,11 @@ if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte))
} }
{ {
ubyte[3] actual = [127, 0, 0]; ubyte[3] actual = [127, 0, 0];
assert(address4(actual[]).isNothing); assert(address4(actual[]).isNull);
} }
{ {
ubyte[5] actual = [127, 0, 0, 0, 1]; ubyte[5] actual = [127, 0, 0, 0, 1];
assert(address4(actual[]).isNothing); assert(address4(actual[]).isNull);
} }
} }
@ -795,10 +796,10 @@ in (digit < 16)
* R = Input range type. * R = Input range type.
* range = Stringish range containing the address. * range = Stringish range containing the address.
* *
* Returns: $(D_PSYMBOL Option) containing the address if the parsing was * Returns: $(D_PSYMBOL Nullable) containing the address if the parsing was
* successful, or nothing otherwise. * successful, or nothing otherwise.
*/ */
Option!Address6 address6(R)(R range) Nullable!Address6 address6(R)(R range)
if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R) if (isForwardRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R)
{ {
if (range.empty) if (range.empty)
@ -1006,10 +1007,10 @@ CopyTail:
* range = $(D_KEYWORD ubyte) range containing the address. * range = $(D_KEYWORD ubyte) range containing the address.
* scopeID = Scope ID. * scopeID = Scope ID.
* *
* Returns: $(D_PSYMBOL Option) containing the address if the $(D_PARAM range) * Returns: $(D_PSYMBOL Nullable) containing the address if the $(D_PARAM range)
* contains exactly 16 bytes, or nothing otherwise. * contains exactly 16 bytes, or nothing otherwise.
*/ */
Option!Address6 address6(R)(R range, uint scopeID = 0) Nullable!Address6 address6(R)(R range, uint scopeID = 0)
if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte)) if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte))
{ {
Address6 result; Address6 result;
@ -1030,20 +1031,20 @@ if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte))
{ {
ubyte[16] actual = [ 1, 2, 3, 4, 5, 6, 7, 8, ubyte[16] actual = [ 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16 ]; 9, 10, 11, 12, 13, 14, 15, 16 ];
assert(!address6(actual[]).isNothing); assert(!address6(actual[]).isNull);
} }
{ {
ubyte[15] actual = [ 1, 2, 3, 4, 5, 6, 7, 8, ubyte[15] actual = [ 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15 ]; 9, 10, 11, 12, 13, 14, 15 ];
assert(address6(actual[]).isNothing); assert(address6(actual[]).isNull);
} }
{ {
ubyte[17] actual = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, ubyte[17] actual = [ 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17 ]; 10, 11, 12, 13, 14, 15, 16, 17 ];
assert(address6(actual[]).isNothing); assert(address6(actual[]).isNull);
} }
{ {
assert(address6(cast(ubyte[]) []).isNothing); assert(address6(cast(ubyte[]) []).isNull);
} }
} }

View File

@ -153,274 +153,6 @@ template tuple(Names...)
assert(t.two == 5); assert(t.two == 5);
} }
/**
* $(D_PSYMBOL Option) is a type that contains an optional value.
*
* Params:
* T = Type of the encapsulated value.
*
* See_Also: $(D_PSYMBOL option).
*/
struct Option(T)
{
private bool isNothing_ = true;
private T value = void;
/**
* Constructs a new option with $(D_PARAM value).
*
* Params:
* value = Encapsulated value.
*/
this()(ref T value)
{
this.value = value;
this.isNothing_ = false;
}
/// ditto
this()(T value) @trusted
{
moveEmplace(value, this.value);
this.isNothing_ = false;
}
/**
* Tells if the option is just a value or nothing.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Option) contains a nothing,
* $(D_KEYWORD false) if it contains a value.
*/
@property bool isNothing() const
{
return this.isNothing_;
}
/**
* Returns the encapsulated value.
*
* Returns: Value encapsulated in this $(D_PSYMBOL Option).
*
* See_Also: $(D_PSYMBOL or).
*
* Precondition: `!isNothing`.
*/
@property ref inout(T) get() inout
in (!isNothing, "Option is nothing")
{
return this.value;
}
/**
* Returns the encapsulated value if available or a default value
* otherwise.
*
* Note that the contained value can be returned by reference only if the
* default value is passed by reference as well.
*
* Params:
* U = Type of the default value.
* defaultValue = Default value.
*
* Returns: The value of this $(D_PSYMBOL Option) if available,
* $(D_PARAM defaultValue) otherwise.
*
* See_Also: $(D_PSYMBOL isNothing), $(D_PSYMBOL get).
*/
@property U or(U)(U defaultValue) inout
if (is(U == T) && isCopyable!T)
{
return isNothing ? defaultValue : this.value;
}
/// ditto
@property ref inout(T) or(ref inout(T) defaultValue) inout
{
return isNothing ? defaultValue : this.value;
}
/**
* Casts this $(D_PSYMBOL Option) to $(D_KEYWORD bool).
*
* An $(D_PSYMBOL Option) is $(D_KEYWORD true) if it contains a value,
* ($D_KEYWORD false) if it contains nothing.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Option) contains a value,
* ($D_KEYWORD false) if it contains nothing.
*/
bool opCast(U : bool)()
{
return !isNothing;
}
/**
* Compares this $(D_PSYMBOL Option) with $(D_PARAM that).
*
* If both objects are options of the same type and they don't contain a
* value, they are considered equal. If only one of them contains a value,
* they aren't equal. Otherwise, the encapsulated values are compared for
* equality.
*
* If $(D_PARAM U) is a type comparable with the type encapsulated by this
* $(D_PSYMBOL Option), the value of this $(D_PSYMBOL Option) is compared
* with $(D_PARAM that), this $(D_PSYMBOL Option) must have a value then.
*
* Params:
* U = Type of the object to compare with.
* that = Object to compare with.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Option) and
* $(D_PARAM that) are equal, $(D_KEYWORD false) if not.
*
* Precondition: `!isNothing` if $(D_PARAM U) is equality comparable with
* $(D_PARAM T).
*/
bool opEquals(U)(auto ref const U that) const
if (is(U == Option))
{
if (!isNothing && !that.isNothing)
{
return this.value == that.value;
}
return isNothing == that.isNothing;
}
/// ditto
bool opEquals(U)(auto ref const U that) const
if (ifTestable!(U, a => a == T.init) && !is(U == Option))
in (!isNothing)
{
return get == that;
}
/**
* Resets this $(D_PSYMBOL Option) and destroys the contained value.
*
* $(D_PSYMBOL reset) can be safely called on an $(D_PSYMBOL Option) that
* doesn't contain any value.
*/
void reset()
{
static if (hasElaborateDestructor!T)
{
destroy(this.value);
}
this.isNothing_ = true;
}
/**
* Assigns a new value.
*
* Params:
* U = Type of the new value.
* that = New value.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(U)(ref U that)
if (is(U : T) && !is(U == Option))
{
this.value = that;
this.isNothing_ = false;
return this;
}
/// ditto
ref typeof(this) opAssign(U)(U that)
if (is(U == T))
{
move(that, this.value);
this.isNothing_ = false;
return this;
}
/// ditto
ref typeof(this) opAssign(U)(ref U that)
if (is(U == Option))
{
if (that.isNothing)
{
reset();
}
else
{
this.value = that.get;
this.isNothing_ = false;
}
return this;
}
/// ditto
ref typeof(this) opAssign(U)(U that)
if (is(U == Option))
{
move(that.value, this.value);
this.isNothing_ = that.isNothing_;
return this;
}
version (D_Ddoc)
{
/**
* If $(D_PARAM T) has a `toHash()` method, $(D_PSYMBOL Option) defines
* `toHash()` which returns `T.toHash()` if it is set or 0 otherwise.
*
* Returns: Hash value.
*/
size_t toHash() const;
}
else static if (is(typeof(T.init.toHash()) == size_t))
{
size_t toHash() const
{
return isNothing ? 0U : this.value.toHash();
}
}
}
///
@nogc nothrow pure @safe unittest
{
Option!int option;
assert(option.isNothing);
assert(option.or(8) == 8);
option = 5;
assert(!option.isNothing);
assert(option.get == 5);
assert(option.or(8) == 5);
option.reset();
assert(option.isNothing);
}
/**
* Creates a new $(D_PSYMBOL Option).
*
* Params:
* T = Option type.
* value = Initial value.
*
* See_Also: $(D_PSYMBOL Option).
*/
Option!T option(T)(auto ref T value)
{
return Option!T(forward!value);
}
/// ditto
Option!T option(T)()
{
return Option!T();
}
///
@nogc nothrow pure @safe unittest
{
assert(option!int().isNothing);
assert(option(5) == 5);
}
/** /**
* Type that can hold one of the types listed as its template parameters. * Type that can hold one of the types listed as its template parameters.
* *

View File

@ -9,17 +9,17 @@ import tanya.range;
// Rejects malformed addresses // Rejects malformed addresses
@nogc nothrow pure @safe unittest @nogc nothrow pure @safe unittest
{ {
assert(address4("256.0.0.1").isNothing); assert(address4("256.0.0.1").isNull);
assert(address4(".0.0.1").isNothing); assert(address4(".0.0.1").isNull);
assert(address4("0..0.1").isNothing); assert(address4("0..0.1").isNull);
assert(address4("0.0.0.").isNothing); assert(address4("0.0.0.").isNull);
assert(address4("0.0.").isNothing); assert(address4("0.0.").isNull);
assert(address4("").isNothing); assert(address4("").isNull);
} }
@nogc nothrow pure @safe unittest @nogc nothrow pure @safe unittest
{ {
assert(address4(cast(ubyte[]) []).isNothing); assert(address4(cast(ubyte[]) []).isNull);
} }
// Assignment and comparison works // Assignment and comparison works
@ -106,12 +106,12 @@ import tanya.range;
// Rejects malformed addresses // Rejects malformed addresses
@nogc nothrow @safe unittest @nogc nothrow @safe unittest
{ {
assert(address6("").isNothing); assert(address6("").isNull);
assert(address6(":").isNothing); assert(address6(":").isNull);
assert(address6(":a").isNothing); assert(address6(":a").isNull);
assert(address6("a:").isNothing); assert(address6("a:").isNull);
assert(address6("1:2:3:4::6:").isNothing); assert(address6("1:2:3:4::6:").isNull);
assert(address6("fe80:2:3:4::6:7:8%").isNothing); assert(address6("fe80:2:3:4::6:7:8%").isNull);
} }
// Parses embedded IPv4 address // Parses embedded IPv4 address
@ -138,9 +138,9 @@ import tanya.range;
@nogc nothrow @safe unittest @nogc nothrow @safe unittest
{ {
assert(address6("0:0:0:0:0:0:1.2.3.").isNothing); assert(address6("0:0:0:0:0:0:1.2.3.").isNull);
assert(address6("0:0:0:0:0:0:1.2:3.4").isNothing); assert(address6("0:0:0:0:0:0:1.2:3.4").isNull);
assert(address6("0:0:0:0:0:0:1.2.3.4.").isNothing); assert(address6("0:0:0:0:0:0:1.2.3.4.").isNull);
assert(address6("fe80:0:0:0:0:0:1.2.3.4%1").get.scopeID == 1); assert(address6("fe80:0:0:0:0:0:1.2.3.4%1").get.scopeID == 1);
} }

View File

@ -22,120 +22,6 @@ import tanya.typecons;
static assert(!is(Tuple!(int, "first", double, "second", char, "third"))); static assert(!is(Tuple!(int, "first", double, "second", char, "third")));
} }
// Assigns a new value
@nogc nothrow pure @safe unittest
{
Option!int option = 5;
option = 8;
assert(!option.isNothing);
assert(option == 8);
}
@nogc nothrow pure @safe unittest
{
Option!int option;
const int newValue = 8;
assert(option.isNothing);
option = newValue;
assert(!option.isNothing);
assert(option == newValue);
}
@nogc nothrow pure @safe unittest
{
Option!int option1;
Option!int option2 = 5;
assert(option1.isNothing);
option1 = option2;
assert(!option1.isNothing);
assert(option1.get == 5);
}
// Constructs with a value passed by reference
@nogc nothrow pure @safe unittest
{
int i = 5;
assert(Option!int(i).get == 5);
}
// Moving
@nogc nothrow pure @safe unittest
{
static assert(is(typeof(Option!NonCopyable(NonCopyable()))));
// The value cannot be returned by reference because the default value
// isn't passed by reference
static assert(!is(typeof(Option!DisabledPostblit().or(NonCopyable()))));
}
@nogc nothrow pure @safe unittest
{
NonCopyable notCopyable;
static assert(is(typeof(Option!NonCopyable().or(notCopyable))));
}
@nogc nothrow pure @safe unittest
{
Option!NonCopyable option;
assert(option.isNothing);
option = NonCopyable();
assert(!option.isNothing);
}
@nogc nothrow pure @safe unittest
{
Option!NonCopyable option;
assert(option.isNothing);
option = Option!NonCopyable(NonCopyable());
assert(!option.isNothing);
}
// Cast to bool is done before touching the encapsulated value
@nogc nothrow pure @safe unittest
{
assert(Option!bool(false));
}
// Option can be const
@nogc nothrow pure @safe unittest
{
assert((const Option!int(5)).get == 5);
assert((const Option!int()).or(5) == 5);
}
// Equality
@nogc nothrow pure @safe unittest
{
assert(Option!int() == Option!int());
assert(Option!int(0) != Option!int());
assert(Option!int(5) == Option!int(5));
assert(Option!int(5) == 5);
assert(Option!int(5) == cast(ubyte) 5);
}
// Returns default value
@nogc nothrow pure @safe unittest
{
int i = 5;
assert(((ref e) => e)(Option!int().or(i)) == 5);
}
// Implements toHash() for nothing
@nogc nothrow pure @safe unittest
{
alias OptionT = Option!Hashable;
assert(OptionT().toHash() == 0U);
assert(OptionT(Hashable(1U)).toHash() == 1U);
}
// Can assign Option that is nothing
@nogc nothrow pure @safe unittest
{
auto option1 = Option!int(5);
Option!int option2;
option1 = option2;
assert(option1.isNothing);
}
@nogc nothrow pure @safe unittest @nogc nothrow pure @safe unittest
{ {
Variant!(int, double) variant; Variant!(int, double) variant;