mirror of
https://github.com/dlang/phobos.git
synced 2025-05-11 14:41:08 +03:00
fix issue 9959 - Add functional pattern matching for object references
Add the function std.algorithm.castSwitch, which chooses a delegate based on the class of an object and run that delegate with that object.
This commit is contained in:
parent
9fdee20ef3
commit
cf1aa96bc4
1 changed files with 325 additions and 3 deletions
328
std/algorithm.d
328
std/algorithm.d
|
@ -14,9 +14,10 @@ $(MYREF findSplitAfter) $(MYREF findSplitBefore) $(MYREF minCount)
|
|||
$(MYREF minPos) $(MYREF mismatch) $(MYREF skipOver) $(MYREF startsWith)
|
||||
$(MYREF until) )
|
||||
)
|
||||
$(TR $(TDNW Comparison) $(TD $(MYREF among) $(MYREF cmp) $(MYREF equal) $(MYREF
|
||||
levenshteinDistance) $(MYREF levenshteinDistanceAndPath) $(MYREF max)
|
||||
$(MYREF min) $(MYREF mismatch) $(MYREF clamp) $(MYREF predSwitch) )
|
||||
$(TR $(TDNW Comparison) $(TD $(MYREF among) $(MYREF castSwitch) $(MYREF cmp)
|
||||
$(MYREF equal) $(MYREF levenshteinDistance) $(MYREF levenshteinDistanceAndPath)
|
||||
$(MYREF max) $(MYREF min) $(MYREF mismatch) $(MYREF clamp) $(MYREF
|
||||
predSwitch))
|
||||
)
|
||||
$(TR $(TDNW Iteration) $(TD $(MYREF filter) $(MYREF filterBidirectional)
|
||||
$(MYREF group) $(MYREF joiner) $(MYREF map) $(MYREF reduce) $(MYREF
|
||||
|
@ -163,6 +164,9 @@ $(LEADINGROW Comparison
|
|||
$(TR $(TDNW $(LREF among)) $(TD Checks if a value is among a set
|
||||
of values, e.g. $(D if (v.among(1, 2, 3)) // `v` is 1, 2 or 3))
|
||||
)
|
||||
$(TR $(TDNW $(LREF castSwitch)) $(TD $(D (new A()).castSwitch((A a)=>1,(B
|
||||
b)=>2)) returns $(D 1).)
|
||||
)
|
||||
$(TR $(TDNW $(LREF cmp)) $(TD $(D cmp("abc", "abcd")) is $(D
|
||||
-1), $(D cmp("abc", "aba")) is $(D 1), and $(D cmp("abc", "abc")) is
|
||||
$(D 0).)
|
||||
|
@ -12920,6 +12924,324 @@ unittest
|
|||
assert(n == 60);
|
||||
}
|
||||
|
||||
// Used in castSwitch to find the first choice that overshadows the last choice
|
||||
// in a tuple.
|
||||
private template indexOfFirstOvershadowingChoiceOnLast(choices...)
|
||||
{
|
||||
alias firstParameterTypes = ParameterTypeTuple!(choices[0]);
|
||||
alias lastParameterTypes = ParameterTypeTuple!(choices[$ - 1]);
|
||||
|
||||
static if (lastParameterTypes.length == 0)
|
||||
{
|
||||
// If the last is null-typed choice, check if the first is null-typed.
|
||||
enum isOvershadowing = firstParameterTypes.length == 0;
|
||||
}
|
||||
else static if (firstParameterTypes.length == 1)
|
||||
{
|
||||
// If the both first and last are not null-typed, check for overshadowing.
|
||||
enum isOvershadowing =
|
||||
is(firstParameterTypes[0] == Object) // Object overshadows all other classes!(this is needed for interfaces)
|
||||
|| is(lastParameterTypes[0] : firstParameterTypes[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the first is null typed and the last is not - the is no overshadowing.
|
||||
enum isOvershadowing = false;
|
||||
}
|
||||
|
||||
static if (isOvershadowing)
|
||||
{
|
||||
enum indexOfFirstOvershadowingChoiceOnLast = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
enum indexOfFirstOvershadowingChoiceOnLast =
|
||||
1 + indexOfFirstOvershadowingChoiceOnLast!(choices[1..$]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Executes and returns one of a collection of handlers based on the type of the
|
||||
switch object.
|
||||
|
||||
$(D choices) needs to be composed of function or delegate handlers that accept
|
||||
one argument. The first choice that $(D switchObject) can be casted to the type
|
||||
of argument it accepts will be called with $(D switchObject) casted to that
|
||||
type, and the value it'll return will be returned by $(D castSwitch).
|
||||
|
||||
There can also be a choice that accepts zero arguments. That choice will be
|
||||
invoked if $(D switchObject) is null.
|
||||
|
||||
If a choice's return type is void, the choice must throw an exception, unless
|
||||
all the choices are void. In that case, castSwitch itself will return void.
|
||||
|
||||
Throws: If none of the choice matches, a $(D SwitchError) will be thrown. $(D
|
||||
SwitchError) will also be thrown if not all the choices are void and a void
|
||||
choice was executed without throwing anything.
|
||||
|
||||
Note: $(D castSwitch) can only be used with object types.
|
||||
*/
|
||||
auto castSwitch(choices...)(Object switchObject)
|
||||
{
|
||||
import core.exception : SwitchError;
|
||||
|
||||
// Check to see if all handlers return void.
|
||||
enum areAllHandlersVoidResult={
|
||||
foreach(index, choice; choices)
|
||||
{
|
||||
if(!is(ReturnType!choice == void))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (switchObject !is null)
|
||||
{
|
||||
|
||||
// Checking for exact matches:
|
||||
ClassInfo classInfo = typeid(switchObject);
|
||||
foreach (index, choice; choices)
|
||||
{
|
||||
static assert(isCallable!choice,
|
||||
"A choice handler must be callable");
|
||||
|
||||
alias choiceParameterTypes = ParameterTypeTuple!choice;
|
||||
static assert(choiceParameterTypes.length <= 1,
|
||||
"A choice handler can not have more than one argument.");
|
||||
|
||||
static if (choiceParameterTypes.length == 1)
|
||||
{
|
||||
alias CastClass = choiceParameterTypes[0];
|
||||
static assert(is(CastClass == class) || is(CastClass == interface),
|
||||
"A choice handler can have only class or interface typed argument.");
|
||||
|
||||
// Check for overshadowing:
|
||||
immutable indexOfOvershadowingChoice =
|
||||
indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]);
|
||||
static assert(indexOfOvershadowingChoice == index,
|
||||
"choice number %d(type %s) is overshadowed by choice number %d(type %s)".format(
|
||||
index + 1, CastClass.stringof, indexOfOvershadowingChoice + 1,
|
||||
ParameterTypeTuple!(choices[indexOfOvershadowingChoice])[0].stringof));
|
||||
|
||||
if (classInfo == typeid(CastClass))
|
||||
{
|
||||
static if(is(ReturnType!(choice) == void))
|
||||
{
|
||||
choice(cast(CastClass) switchObject);
|
||||
static if (areAllHandlersVoidResult)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SwitchError("Handlers that return void should throw");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return choice(cast(CastClass) switchObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checking for derived matches:
|
||||
foreach (choice; choices)
|
||||
{
|
||||
alias choiceParameterTypes = ParameterTypeTuple!choice;
|
||||
static if (choiceParameterTypes.length == 1)
|
||||
{
|
||||
if (auto castedObject = cast(choiceParameterTypes[0]) switchObject)
|
||||
{
|
||||
static if(is(ReturnType!(choice) == void))
|
||||
{
|
||||
choice(castedObject);
|
||||
static if (areAllHandlersVoidResult)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SwitchError("Handlers that return void should throw");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return choice(castedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // If switchObject is null:
|
||||
{
|
||||
// Checking for null matches:
|
||||
foreach (index, choice; choices)
|
||||
{
|
||||
static if (ParameterTypeTuple!(choice).length == 0)
|
||||
{
|
||||
immutable indexOfOvershadowingChoice =
|
||||
indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]);
|
||||
|
||||
// Check for overshadowing:
|
||||
static assert(indexOfOvershadowingChoice == index,
|
||||
"choice number %d(null reference) is overshadowed by choice number %d(null reference)".format(
|
||||
index + 1, indexOfOvershadowingChoice + 1));
|
||||
|
||||
if (switchObject is null)
|
||||
{
|
||||
static if(is(ReturnType!(choice) == void))
|
||||
{
|
||||
choice();
|
||||
static if (areAllHandlersVoidResult)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SwitchError("Handlers that return void should throw");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return choice();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In case nothing matched:
|
||||
throw new SwitchError("Input not matched by any choice");
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
import std.string : format;
|
||||
|
||||
class A
|
||||
{
|
||||
int a;
|
||||
this(int a) {this.a = a;}
|
||||
@property int i() { return a; }
|
||||
}
|
||||
interface I { }
|
||||
class B : I { }
|
||||
|
||||
Object[] arr = [new A(1), new B(), null];
|
||||
|
||||
auto results = arr.map!(castSwitch!(
|
||||
(A a) => "A with a value of %d".format(a.a),
|
||||
(I i) => "derived from I",
|
||||
() => "null reference",
|
||||
))();
|
||||
|
||||
// A is handled directly:
|
||||
assert(results[0] == "A with a value of 1");
|
||||
// B has no handler - it is handled by the handler of I:
|
||||
assert(results[1] == "derived from I");
|
||||
// null is handled by the null handler:
|
||||
assert(results[2] == "null reference");
|
||||
}
|
||||
|
||||
/// Using with void handlers:
|
||||
unittest
|
||||
{
|
||||
import std.exception : assertThrown;
|
||||
|
||||
class A { }
|
||||
class B { }
|
||||
// Void handlers are allowed if they throw:
|
||||
assertThrown!Exception(
|
||||
new B().castSwitch!(
|
||||
(A a) => 1,
|
||||
(B d) { throw new Exception("B is not allowed!"); }
|
||||
)()
|
||||
);
|
||||
|
||||
// Void handlers are also allowed if all the handlers are void:
|
||||
new A().castSwitch!(
|
||||
(A a) { assert(true); },
|
||||
(B b) { assert(false); },
|
||||
)();
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
import core.exception : SwitchError;
|
||||
import std.exception : assertThrown;
|
||||
|
||||
interface I { }
|
||||
class A : I { }
|
||||
class B { }
|
||||
|
||||
// Nothing matches:
|
||||
assertThrown!SwitchError((new A()).castSwitch!(
|
||||
(B b) => 1,
|
||||
() => 2,
|
||||
)());
|
||||
|
||||
// Choices with multiple arguments are not allowed:
|
||||
static assert(!__traits(compiles,
|
||||
(new A()).castSwitch!(
|
||||
(A a, B b) => 0,
|
||||
)()));
|
||||
|
||||
// Only callable handlers allowed:
|
||||
static assert(!__traits(compiles,
|
||||
(new A()).castSwitch!(
|
||||
1234,
|
||||
)()));
|
||||
|
||||
// Only object arguments allowed:
|
||||
static assert(!__traits(compiles,
|
||||
(new A()).castSwitch!(
|
||||
(int x) => 0,
|
||||
)()));
|
||||
|
||||
// Object overshadows regular classes:
|
||||
static assert(!__traits(compiles,
|
||||
(new A()).castSwitch!(
|
||||
(Object o) => 0,
|
||||
(A a) => 1,
|
||||
)()));
|
||||
|
||||
// Object overshadows interfaces:
|
||||
static assert(!__traits(compiles,
|
||||
(new A()).castSwitch!(
|
||||
(Object o) => 0,
|
||||
(I i) => 1,
|
||||
)()));
|
||||
|
||||
// No multiple null handlers allowed:
|
||||
static assert(!__traits(compiles,
|
||||
(new A()).castSwitch!(
|
||||
() => 0,
|
||||
() => 1,
|
||||
)()));
|
||||
|
||||
// No non-throwing void handlers allowed(when there are non-void handlers):
|
||||
assertThrown!SwitchError((new A()).castSwitch!(
|
||||
(A a) {},
|
||||
(B b) => 2,
|
||||
)());
|
||||
|
||||
// All-void handlers work for the null case:
|
||||
null.castSwitch!(
|
||||
(Object o) { assert(false); },
|
||||
() { assert(true); },
|
||||
)();
|
||||
|
||||
// Throwing void handlers work for the null case:
|
||||
assertThrown!Exception(null.castSwitch!(
|
||||
(Object o) => 1,
|
||||
() { throw new Exception("null"); },
|
||||
)());
|
||||
}
|
||||
|
||||
// cartesianProduct
|
||||
/**
|
||||
Lazily computes the Cartesian product of two or more ranges. The product is a
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue