Skip to content

Commit 81beb9a

Browse files
committed
Add procedural API
This change introduces three new convenience functions that allow code using SumType to be written in a more procedural style (as opposed to the functional style of match). * has!T checks whether a SumType contains a value of type T. * get!T accesses a SumType's value if it has type T, or asserts if it does not. * tryGet!T is like get!T, but throws an exception instead of asserting on failure. To make room for get!T, the existing SumType.get method is renamed to getByIndex. Phobos PR: dlang/phobos#10650
1 parent b35b1b1 commit 81beb9a

1 file changed

Lines changed: 384 additions & 0 deletions

File tree

src/sumtype.d

Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,6 +2490,390 @@ static if (haveDip1000 && __VERSION__ >= 2100)
24902490
}));
24912491
}
24922492

2493+
/**
2494+
* Checks whether a `SumType` contains a value of a given type.
2495+
*
2496+
* The types must match exactly, without implicit conversions.
2497+
*
2498+
* Params:
2499+
* T = the type to check for.
2500+
*/
2501+
template has(T)
2502+
{
2503+
/**
2504+
* The actual `has` function.
2505+
*
2506+
* Params:
2507+
* self = the `SumType` to check.
2508+
*
2509+
* Returns: true if `self` contains a `T`, otherwise false.
2510+
*/
2511+
bool has(Self)(auto ref Self self)
2512+
if (isSumType!Self)
2513+
{
2514+
return self.match!checkType;
2515+
}
2516+
2517+
// Helper to avoid redundant template instantiations
2518+
private bool checkType(Value)(ref Value value)
2519+
{
2520+
return is(Value == T);
2521+
}
2522+
}
2523+
2524+
/// Basic usage
2525+
@safe unittest {
2526+
SumType!(string, double) example = "hello";
2527+
2528+
assert( example.has!string);
2529+
assert(!example.has!double);
2530+
2531+
// If T isn't part of the SumType, has!T will always return false
2532+
assert(!example.has!int);
2533+
}
2534+
2535+
/// With type qualifiers
2536+
@safe unittest {
2537+
alias Example = SumType!(string, double);
2538+
2539+
Example m = "mutable";
2540+
const Example c = "const";
2541+
immutable Example i = "immutable";
2542+
2543+
assert( m.has!string);
2544+
assert(!m.has!(const(string)));
2545+
assert(!m.has!(immutable(string)));
2546+
2547+
assert(!c.has!string);
2548+
assert( c.has!(const(string)));
2549+
assert(!c.has!(immutable(string)));
2550+
2551+
assert(!i.has!string);
2552+
assert(!i.has!(const(string)));
2553+
assert( i.has!(immutable(string)));
2554+
}
2555+
2556+
/// As a predicate
2557+
version (D_BetterC) {} else
2558+
@safe unittest {
2559+
import std.algorithm.iteration: filter;
2560+
import std.algorithm.comparison: equal;
2561+
2562+
alias Example = SumType!(string, double);
2563+
2564+
auto arr = [
2565+
Example("foo"),
2566+
Example(0),
2567+
Example("bar"),
2568+
Example(1),
2569+
Example(2),
2570+
Example("baz")
2571+
];
2572+
2573+
auto strings = arr.filter!(has!string);
2574+
auto nums = arr.filter!(has!double);
2575+
2576+
assert(strings.equal([Example("foo"), Example("bar"), Example("baz")]));
2577+
assert(nums.equal([Example(0), Example(1), Example(2)]));
2578+
}
2579+
2580+
// Non-copyable types
2581+
@safe unittest {
2582+
static struct NoCopy
2583+
{
2584+
@disable this(this);
2585+
}
2586+
2587+
SumType!NoCopy x;
2588+
2589+
assert(x.has!NoCopy);
2590+
}
2591+
2592+
/**
2593+
* Accesses a `SumType`'s value.
2594+
*
2595+
* The value must be of the specified type. Use [has] to check.
2596+
*
2597+
* Params:
2598+
* T = the type of the value being accessed.
2599+
*/
2600+
template get(T)
2601+
{
2602+
/**
2603+
* The actual `get` function.
2604+
*
2605+
* Params:
2606+
* self = the `SumType` whose value is being accessed.
2607+
*
2608+
* Returns: the `SumType`'s value.
2609+
*/
2610+
auto ref T get(Self)(auto ref Self self)
2611+
if (isSumType!Self)
2612+
{
2613+
import std.typecons : No;
2614+
2615+
static if (__traits(isRef, self))
2616+
return self.match!(getLvalue!(No.try_, T));
2617+
else
2618+
return self.match!(getRvalue!(No.try_, T));
2619+
}
2620+
}
2621+
2622+
/// Basic usage
2623+
@safe unittest {
2624+
SumType!(string, double) example1 = "hello";
2625+
SumType!(string, double) example2 = 3.14;
2626+
2627+
assert(example1.get!string == "hello");
2628+
assert(example2.get!double == 3.14);
2629+
}
2630+
2631+
/// With type qualifiers
2632+
@safe unittest {
2633+
alias Example = SumType!(string, double);
2634+
2635+
Example m = "mutable";
2636+
const(Example) c = "const";
2637+
immutable(Example) i = "immutable";
2638+
2639+
assert(m.get!string == "mutable");
2640+
assert(c.get!(const(string)) == "const");
2641+
assert(i.get!(immutable(string)) == "immutable");
2642+
}
2643+
2644+
/// As a predicate
2645+
version (D_BetterC) {} else
2646+
@safe unittest {
2647+
import std.algorithm.iteration: map;
2648+
import std.algorithm.comparison: equal;
2649+
2650+
alias Example = SumType!(string, double);
2651+
2652+
auto arr = [Example(0), Example(1), Example(2)];
2653+
auto values = arr.map!(get!double);
2654+
2655+
assert(values.equal([0, 1, 2]));
2656+
}
2657+
2658+
// Non-copyable types
2659+
@safe unittest {
2660+
static struct NoCopy
2661+
{
2662+
@disable this(this);
2663+
}
2664+
2665+
SumType!NoCopy lvalue;
2666+
auto rvalue() { return SumType!NoCopy(); }
2667+
2668+
assert(lvalue.get!NoCopy == NoCopy());
2669+
assert(rvalue.get!NoCopy == NoCopy());
2670+
}
2671+
2672+
// Immovable rvalues
2673+
@safe unittest {
2674+
auto rvalue() { return const(SumType!string)("hello"); }
2675+
2676+
assert(rvalue.get!(const(string)) == "hello");
2677+
}
2678+
2679+
// Nontrivial rvalues at compile time
2680+
@safe unittest {
2681+
static struct ElaborateCopy
2682+
{
2683+
this(this) {}
2684+
}
2685+
2686+
enum rvalue = SumType!ElaborateCopy();
2687+
enum ctResult = rvalue.get!ElaborateCopy;
2688+
2689+
assert(ctResult == ElaborateCopy());
2690+
}
2691+
2692+
/**
2693+
* Attempt to access a `SumType`'s value.
2694+
*
2695+
* If the `SumType` does not contain a value of the specified type, an
2696+
* exception is thrown.
2697+
*
2698+
* Params:
2699+
* T = the type of the value being accessed.
2700+
*/
2701+
version (D_Exceptions)
2702+
template tryGet(T)
2703+
{
2704+
/**
2705+
* The actual `tryGet` function.
2706+
*
2707+
* Params:
2708+
* self = the `SumType` whose value is being accessed.
2709+
*
2710+
* Throws: `MatchException` if the value does not have the expected type.
2711+
*
2712+
* Returns: the `SumType`'s value.
2713+
*/
2714+
auto ref T tryGet(Self)(auto ref Self self)
2715+
if (isSumType!Self)
2716+
{
2717+
import std.typecons: Yes;
2718+
2719+
static if (__traits(isRef, self))
2720+
return self.match!(getLvalue!(Yes.try_, T));
2721+
else
2722+
return self.match!(getRvalue!(Yes.try_, T));
2723+
}
2724+
}
2725+
2726+
/// Basic usage
2727+
version (D_Exceptions)
2728+
@safe unittest {
2729+
SumType!(string, double) example = "hello";
2730+
2731+
assert(example.tryGet!string == "hello");
2732+
2733+
double result = double.nan;
2734+
try
2735+
result = example.tryGet!double;
2736+
catch (MatchException e)
2737+
result = 0;
2738+
2739+
// Exception was thrown
2740+
assert(result == 0);
2741+
}
2742+
2743+
/// With type qualifiers
2744+
version (D_Exceptions)
2745+
@safe unittest {
2746+
import std.exception: assertThrown;
2747+
2748+
const(SumType!(string, double)) example = "const";
2749+
2750+
// Qualifier mismatch; throws exception
2751+
assertThrown!MatchException(example.tryGet!string);
2752+
// Qualifier matches; no exception
2753+
assert(example.tryGet!(const(string)) == "const");
2754+
}
2755+
2756+
/// As a predicate
2757+
version (D_BetterC) {} else
2758+
@safe unittest {
2759+
import std.algorithm.iteration: map, sum;
2760+
import std.functional: pipe;
2761+
import std.exception: assertThrown;
2762+
2763+
alias Example = SumType!(string, double);
2764+
2765+
auto arr1 = [Example(0), Example(1), Example(2)];
2766+
auto arr2 = [Example("foo"), Example("bar"), Example("baz")];
2767+
2768+
alias trySum = pipe!(map!(tryGet!double), sum);
2769+
2770+
assert(trySum(arr1) == 0 + 1 + 2);
2771+
assertThrown!MatchException(trySum(arr2));
2772+
}
2773+
2774+
// Throws if requested type is impossible
2775+
version (D_Exceptions)
2776+
@safe unittest {
2777+
import std.exception: assertThrown;
2778+
2779+
SumType!int x;
2780+
2781+
assertThrown!MatchException(x.tryGet!string);
2782+
}
2783+
2784+
// Non-copyable types
2785+
version (D_Exceptions)
2786+
@safe unittest {
2787+
static struct NoCopy
2788+
{
2789+
@disable this(this);
2790+
}
2791+
2792+
SumType!NoCopy lvalue;
2793+
auto rvalue() { return SumType!NoCopy(); }
2794+
2795+
assert(lvalue.tryGet!NoCopy == NoCopy());
2796+
assert(rvalue.tryGet!NoCopy == NoCopy());
2797+
}
2798+
2799+
// Immovable types
2800+
version (D_Exceptions)
2801+
@safe unittest {
2802+
auto rvalue() { return const(SumType!string)("hello"); }
2803+
2804+
assert(rvalue.tryGet!(const(string)) == "hello");
2805+
}
2806+
2807+
// Nontrivial rvalues at compile time
2808+
version (D_Exceptions)
2809+
@safe unittest {
2810+
static struct ElaborateCopy
2811+
{
2812+
this(this) {}
2813+
}
2814+
2815+
enum rvalue = SumType!ElaborateCopy();
2816+
enum ctResult = rvalue.tryGet!ElaborateCopy;
2817+
2818+
assert(ctResult == ElaborateCopy());
2819+
}
2820+
2821+
private template failedGetMessage(Expected, Actual)
2822+
{
2823+
static if (Expected.stringof == Actual.stringof) {
2824+
enum expectedStr = __traits(fullyQualifiedName, Expected);
2825+
enum actualStr = __traits(fullyQualifiedName, Actual);
2826+
} else {
2827+
enum expectedStr = Expected.stringof;
2828+
enum actualStr = Actual.stringof;
2829+
}
2830+
2831+
enum failedGetMessage =
2832+
"Tried to get `" ~ expectedStr ~ "`" ~
2833+
" but found `" ~ actualStr ~ "`";
2834+
}
2835+
2836+
private template getLvalue(Flag!"try_" try_, T)
2837+
{
2838+
ref T getLvalue(Value)(ref Value value)
2839+
{
2840+
static if (is(Value == T)) {
2841+
return value;
2842+
} else {
2843+
static if (try_)
2844+
throw new MatchException(failedGetMessage!(T, Value));
2845+
else
2846+
assert(false, failedGetMessage!(T, Value));
2847+
}
2848+
}
2849+
}
2850+
2851+
private template getRvalue(Flag!"try_" try_, T)
2852+
{
2853+
T getRvalue(Value)(ref Value value)
2854+
{
2855+
static if (is(Value == T)) {
2856+
import core.lifetime: move;
2857+
2858+
// Move if possible; otherwise fall back to copy
2859+
static if (is(typeof(move(value)))) {
2860+
static if (isCopyable!Value)
2861+
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
2862+
return __ctfe ? value : move(value);
2863+
else
2864+
return move(value);
2865+
} else {
2866+
return value;
2867+
}
2868+
} else {
2869+
static if (try_)
2870+
throw new MatchException(failedGetMessage!(T, Value));
2871+
else
2872+
assert(false, failedGetMessage!(T, Value));
2873+
}
2874+
}
2875+
}
2876+
24932877
private void destroyIfOwner(T)(ref T value)
24942878
{
24952879
static if (hasElaborateDestructor!T) {

0 commit comments

Comments
 (0)