diff options
Diffstat (limited to 'libphobos/src/std/functional.d')
-rw-r--r-- | libphobos/src/std/functional.d | 688 |
1 files changed, 470 insertions, 218 deletions
diff --git a/libphobos/src/std/functional.d b/libphobos/src/std/functional.d index f35d6ff..cec61fe 100644 --- a/libphobos/src/std/functional.d +++ b/libphobos/src/std/functional.d @@ -8,6 +8,7 @@ functions are helpful when constructing predicates for the algorithms in $(MREF std, algorithm) or $(MREF std, range). $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Function Name) $(TH Description) ) @@ -20,9 +21,6 @@ $(TR $(TH Function Name) $(TH Description) functions one after the other, using one function's result for the next function's argument. )) - $(TR $(TD $(LREF forward)) - $(TD Forwards function arguments while saving ref-ness. - )) $(TR $(TD $(LREF lessThan), $(LREF greaterThan), $(LREF equalTo)) $(TD Ready-made predicate functions to compare two values. )) @@ -36,7 +34,11 @@ $(TR $(TH Function Name) $(TH Description) $(TD Creates a function that binds the first argument of a given function to a given value. )) - $(TR $(TD $(LREF reverseArgs), $(LREF binaryReverseArgs)) + $(TR $(TD $(LREF curry)) + $(TD Converts a multi-argument function into a series of single-argument + functions. f(x, y) == curry(f)(x)(y) + )) + $(TR $(TD $(LREF reverseArgs)) $(TD Predicate that reverses the order of its arguments. )) $(TR $(TD $(LREF toDelegate)) @@ -46,12 +48,12 @@ $(TR $(TH Function Name) $(TH Description) $(TD Create a unary or binary function from a string. Most often used when defining algorithms on ranges. )) -) +)) Copyright: Copyright Andrei Alexandrescu 2008 - 2009. License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.org, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/_functional.d) +Source: $(PHOBOSSRC std/functional.d) */ /* Copyright Andrei Alexandrescu 2008 - 2009. @@ -61,9 +63,10 @@ Distributed under the Boost Software License, Version 1.0. */ module std.functional; -import std.meta; // AliasSeq, Reverse -import std.traits; // isCallable, Parameters +import std.meta : AliasSeq, Reverse; +import std.traits : isCallable, Parameters; +import std.internal.attributes : betterC; private template needOpCallAlias(alias fun) { @@ -93,12 +96,19 @@ private template needOpCallAlias(alias fun) } /** -Transforms a string representing an expression into a unary -function. The string must either use symbol name $(D a) as -the parameter or provide the symbol via the $(D parmName) argument. -If $(D fun) is not a string, $(D unaryFun) aliases itself away to $(D fun). +Transforms a `string` representing an expression into a unary +function. The `string` must either use symbol name `a` as +the parameter or provide the symbol via the `parmName` argument. + +Params: + fun = a `string` or a callable + parmName = the name of the parameter if `fun` is a string. Defaults + to `"a"`. +Returns: + If `fun` is a `string`, a new single parameter function + + If `fun` is not a `string`, an alias to `fun`. */ - template unaryFun(alias fun, string parmName = "a") { static if (is(typeof(fun) : string)) @@ -116,7 +126,7 @@ template unaryFun(alias fun, string parmName = "a") } else static if (needOpCallAlias!fun) { - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 alias unaryFun = fun.opCall; } else @@ -147,7 +157,7 @@ template unaryFun(alias fun, string parmName = "a") int num = 41; assert(unaryFun!"a + 1"(num) == 42); - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 struct Seen { static bool opCall(int n) { return true; } @@ -176,13 +186,20 @@ template unaryFun(alias fun, string parmName = "a") } /** -Transforms a string representing an expression into a binary function. The -string must either use symbol names $(D a) and $(D b) as the parameters or -provide the symbols via the $(D parm1Name) and $(D parm2Name) arguments. -If $(D fun) is not a string, $(D binaryFun) aliases itself away to -$(D fun). +Transforms a `string` representing an expression into a binary function. The +`string` must either use symbol names `a` and `b` as the parameters or +provide the symbols via the `parm1Name` and `parm2Name` arguments. + +Params: + fun = a `string` or a callable + parm1Name = the name of the first parameter if `fun` is a string. + Defaults to `"a"`. + parm2Name = the name of the second parameter if `fun` is a string. + Defaults to `"b"`. +Returns: + If `fun` is not a string, `binaryFun` aliases itself away to + `fun`. */ - template binaryFun(alias fun, string parm1Name = "a", string parm2Name = "b") { @@ -203,7 +220,7 @@ template binaryFun(alias fun, string parm1Name = "a", } else static if (needOpCallAlias!fun) { - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 alias binaryFun = fun.opCall; } else @@ -233,7 +250,7 @@ template binaryFun(alias fun, string parm1Name = "a", //@@BUG //assert(binaryFun!("return a + b;")(41, 1) == 42); - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 struct Seen { static bool opCall(int x, int y) { return true; } @@ -301,7 +318,7 @@ private uint _ctfeSkipName(ref string op, string name) return 0; } -// returns 1 if $(D fun) is trivial unary function +// returns 1 if `fun` is trivial unary function private uint _ctfeMatchUnary(string fun, string name) { if (!__ctfe) assert(false); @@ -348,7 +365,7 @@ private uint _ctfeMatchUnary(string fun, string name) static assert(_ctfeMatchUnary("ё[21]", "ё")); } -// returns 1 if $(D fun) is trivial binary function +// returns 1 if `fun` is trivial binary function private uint _ctfeMatchBinary(string fun, string name1, string name2) { if (!__ctfe) assert(false); @@ -531,13 +548,18 @@ alias equalTo = safeOp!"=="; assert(!equalTo(-1, ~0U)); } /** - N-ary predicate that reverses the order of arguments, e.g., given - $(D pred(a, b, c)), returns $(D pred(c, b, a)). +N-ary predicate that reverses the order of arguments, e.g., given +$(D pred(a, b, c)), returns $(D pred(c, b, a)). + +Params: + pred = A callable +Returns: + A function which calls `pred` after reversing the given parameters */ template reverseArgs(alias pred) { auto reverseArgs(Args...)(auto ref Args args) - if (is(typeof(pred(Reverse!args)))) + if (is(typeof(pred(Reverse!args)))) { return pred(Reverse!args); } @@ -548,6 +570,24 @@ template reverseArgs(alias pred) { alias gt = reverseArgs!(binaryFun!("a < b")); assert(gt(2, 1) && !gt(1, 1)); +} + +/// +@safe unittest +{ + int x = 42; + bool xyz(int a, int b) { return a * x < b / x; } + auto foo = &xyz; + foo(4, 5); + alias zyx = reverseArgs!(foo); + assert(zyx(5, 4) == foo(4, 5)); +} + +/// +@safe unittest +{ + alias gt = reverseArgs!(binaryFun!("a < b")); + assert(gt(2, 1) && !gt(1, 1)); int x = 42; bool xyz(int a, int b) { return a * x < b / x; } auto foo = &xyz; @@ -581,38 +621,13 @@ template reverseArgs(alias pred) } /** - Binary predicate that reverses the order of arguments, e.g., given - $(D pred(a, b)), returns $(D pred(b, a)). -*/ -template binaryReverseArgs(alias pred) -{ - auto binaryReverseArgs(ElementType1, ElementType2) - (auto ref ElementType1 a, auto ref ElementType2 b) - { - return pred(b, a); - } -} - -/// -@safe unittest -{ - alias gt = binaryReverseArgs!(binaryFun!("a < b")); - assert(gt(2, 1) && !gt(1, 1)); -} - -/// -@safe unittest -{ - int x = 42; - bool xyz(int a, int b) { return a * x < b / x; } - auto foo = &xyz; - foo(4, 5); - alias zyx = binaryReverseArgs!(foo); - assert(zyx(5, 4) == foo(4, 5)); -} +Negates predicate `pred`. -/** -Negates predicate $(D pred). +Params: + pred = A string or a callable +Returns: + A function which calls `pred` and returns the logical negation of its + return value. */ template not(alias pred) { @@ -633,7 +648,6 @@ template not(alias pred) @safe unittest { import std.algorithm.searching : find; - import std.functional; import std.uni : isWhite; string a = " Hello, world!"; assert(find!(not!isWhite)(a) == "Hello, world!"); @@ -653,18 +667,20 @@ template not(alias pred) /** $(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially applies) $(D_PARAM fun) by tying its first argument to $(D_PARAM arg). + +Params: + fun = A callable + arg = The first argument to apply to `fun` +Returns: + A new function which calls `fun` with `arg` plus the passed parameters. */ template partial(alias fun, alias arg) { - static if (is(typeof(fun) == delegate) || is(typeof(fun) == function)) - { - import std.traits : ReturnType; - ReturnType!fun partial(Parameters!fun[1..$] args2) - { - return fun(arg, args2); - } - } - else + import std.traits : isCallable; + // Check whether fun is a user defined type which implements opCall or a template. + // As opCall itself can be templated, std.traits.isCallable does not work here. + enum isSomeFunctor = (is(typeof(fun) == struct) || is(typeof(fun) == class)) && __traits(hasMember, fun, "opCall"); + static if (isSomeFunctor || __traits(isTemplate, fun)) { auto partial(Ts...)(Ts args2) { @@ -687,6 +703,36 @@ template partial(alias fun, alias arg) } } } + else static if (!isCallable!fun) + { + static assert(false, "Cannot apply partial to a non-callable '" ~ fun.stringof ~ "'."); + } + else // Assume fun is callable and uniquely defined. + { + static if (Parameters!fun.length == 0) + { + static assert(0, "Cannot partially apply '" ~ fun.stringof ~ "'." ~ + "'" ~ fun.stringof ~ "' has 0 arguments."); + } + else static if (!is(typeof(arg) : Parameters!fun[0])) + { + string errorMsg() + { + string msg = "Argument mismatch for '" ~ fun.stringof ~ "': expected " ~ + Parameters!fun[0].stringof ~ ", but got " ~ typeof(arg).stringof ~ "."; + return msg; + } + static assert(0, errorMsg()); + } + else + { + import std.traits : ReturnType; + ReturnType!fun partial(Parameters!fun[1..$] args2) + { + return fun(arg, args2); + } + } + } } /// @@ -773,6 +819,11 @@ template partial(alias fun, alias arg) assert(partial!(tcallable, 5)(6) == 11); static assert(!is(typeof(partial!(tcallable, "5")(6)))); + static struct NonCallable{} + static assert(!__traits(compiles, partial!(NonCallable, 5)), "Partial should not work on non-callable structs."); + static assert(!__traits(compiles, partial!(NonCallable.init, 5)), + "Partial should not work on instances of non-callable structs."); + static A funOneArg(A)(A a) { return a; } alias funOneArg1 = partial!(funOneArg, 1); assert(funOneArg1() == 1); @@ -786,15 +837,223 @@ template partial(alias fun, alias arg) assert(dg2() == 1); } +// Fix https://issues.dlang.org/show_bug.cgi?id=15732 +@safe unittest +{ + // Test whether it works with functions. + auto partialFunction(){ + auto fullFunction = (float a, float b, float c) => a + b / c; + alias apply1 = partial!(fullFunction, 1); + return &apply1; + } + auto result = partialFunction()(2, 4); + assert(result == 1.5f); + + // And with delegates. + auto partialDelegate(float c){ + auto fullDelegate = (float a, float b) => a + b / c; + alias apply1 = partial!(fullDelegate, 1); + return &apply1; + } + auto result2 = partialDelegate(4)(2); + assert(result2 == 1.5f); +} + /** -Takes multiple functions and adjoins them together. The result is a -$(REF Tuple, std,typecons) with one element per passed-in function. Upon -invocation, the returned tuple is the adjoined results of all -functions. +Takes a function of (potentially) many arguments, and returns a function taking +one argument and returns a callable taking the rest. f(x, y) == curry(f)(x)(y) + +Params: + F = a function taking at least one argument + t = a callable object whose opCall takes at least 1 object +Returns: + A single parameter callable object +*/ +template curry(alias F) +if (isCallable!F && Parameters!F.length) +{ + //inspired from the implementation from Artur Skawina here: + //https://forum.dlang.org/post/mailman.1626.1340110492.24740.digitalmars-d@puremagic.com + //This implementation stores a copy of all filled in arguments with each curried result + //this way, the curried functions are independent and don't share any references + //eg: auto fc = curry!f; auto fc1 = fc(1); auto fc2 = fc(2); fc1(3) != fc2(3) + struct CurryImpl(size_t N) + { + alias FParams = Parameters!F; + FParams[0 .. N] storedArguments; + static if (N > 0) + { + this(U : FParams[N - 1])(ref CurryImpl!(N - 1) prev, ref U arg) + { + storedArguments[0 .. N - 1] = prev.storedArguments[]; + storedArguments[N-1] = arg; + } + } + + auto opCall(U : FParams[N])(auto ref U arg) return scope + { + static if (N == FParams.length - 1) + { + return F(storedArguments, arg); + } + else + { + return CurryImpl!(N + 1)(this, arg); + } + } + } + + auto curry() + { + CurryImpl!0 res; + return res; // return CurryImpl!0.init segfaults for delegates on Windows + } +} + +/// +pure @safe @nogc nothrow unittest +{ + int f(int x, int y, int z) + { + return x + y + z; + } + auto cf = curry!f; + auto cf1 = cf(1); + auto cf2 = cf(2); + + assert(cf1(2)(3) == f(1, 2, 3)); + assert(cf2(2)(3) == f(2, 2, 3)); +} + +///ditto +auto curry(T)(T t) +if (isCallable!T && Parameters!T.length) +{ + static auto fun(ref T inst, ref Parameters!T args) + { + return inst(args); + } + + return curry!fun()(t); +} + +/// +pure @safe @nogc nothrow unittest +{ + //works with callable structs too + struct S + { + int w; + int opCall(int x, int y, int z) + { + return w + x + y + z; + } + } + + S s; + s.w = 5; + + auto cs = curry(s); + auto cs1 = cs(1); + auto cs2 = cs(2); + + assert(cs1(2)(3) == s(1, 2, 3)); + assert(cs1(2)(3) == (1 + 2 + 3 + 5)); + assert(cs2(2)(3) ==s(2, 2, 3)); +} + + +@safe pure @nogc nothrow unittest +{ + //currying a single argument function does nothing + int pork(int a){ return a*2;} + auto curryPork = curry!pork; + assert(curryPork(0) == pork(0)); + assert(curryPork(1) == pork(1)); + assert(curryPork(-1) == pork(-1)); + assert(curryPork(1000) == pork(1000)); + + //test 2 argument function + double mixedVeggies(double a, int b, bool) + { + return a + b; + } + + auto mixedCurry = curry!mixedVeggies; + assert(mixedCurry(10)(20)(false) == mixedVeggies(10, 20, false)); + assert(mixedCurry(100)(200)(true) == mixedVeggies(100, 200, true)); + + // struct with opCall + struct S + { + double opCall(int x, double y, short z) const pure nothrow @nogc + { + return x*y*z; + } + } + + S s; + auto curriedStruct = curry(s); + assert(curriedStruct(1)(2)(short(3)) == s(1, 2, short(3))); + assert(curriedStruct(300)(20)(short(10)) == s(300, 20, short(10))); +} + +pure @safe nothrow unittest +{ + auto cfl = curry!((double a, int b) => a + b); + assert(cfl(13)(2) == 15); + + int c = 42; + auto cdg = curry!((double a, int b) => a + b + c); + assert(cdg(13)(2) == 57); + + static class C + { + int opCall(int mult, int add) pure @safe nothrow @nogc scope + { + return mult * 42 + add; + } + } + + scope C ci = new C(); + scope cc = curry(ci); + assert(cc(2)(4) == ci(2, 4)); +} + +// Disallows callables without parameters +pure @safe @nogc nothrow unittest +{ + static void noargs() {} + static assert(!__traits(compiles, curry!noargs())); + + static struct NoArgs + { + void opCall() {} + } + + static assert(!__traits(compiles, curry(NoArgs.init))); +} + +private template Iota(size_t n) +{ + static if (n == 0) + alias Iota = AliasSeq!(); + else + alias Iota = AliasSeq!(Iota!(n - 1), n - 1); +} + +/** +Takes multiple functions and adjoins them together. + +Params: + F = the call-able(s) to adjoin +Returns: + A new function which returns a $(REF Tuple, std,typecons). Each of the + elements of the tuple will be the return values of `F`. Note: In the special case where only a single function is provided ($(D F.length == 1)), adjoin simply aliases to the single passed function -($(D F[0])). +(`F[0]`). */ template adjoin(F...) if (F.length == 1) @@ -808,27 +1067,21 @@ if (F.length > 1) auto adjoin(V...)(auto ref V a) { import std.typecons : tuple; - static if (F.length == 2) - { - return tuple(F[0](a), F[1](a)); - } - else static if (F.length == 3) - { - return tuple(F[0](a), F[1](a), F[2](a)); - } - else + import std.meta : staticMap; + + auto resultElement(size_t i)() { - import std.format : format; - import std.range : iota; - return mixin (q{tuple(%(F[%s](a)%|, %))}.format(iota(0, F.length))); + return F[i](a); } + + return tuple(staticMap!(resultElement, Iota!(F.length))); } } /// @safe unittest { - import std.functional, std.typecons : Tuple; + import std.typecons : Tuple; static bool f1(int a) { return a != 0; } static int f2(int a) { return a / 2; } auto x = adjoin!(f1, f2)(5); @@ -881,37 +1134,49 @@ if (F.length > 1) enum Tuple!(IS, IS, IS, IS) ret2 = adjoin!(bar, bar, bar, bar)(); } +// https://issues.dlang.org/show_bug.cgi?id=21347 +@safe @betterC unittest +{ + alias f = (int n) => n + 1; + alias g = (int n) => n + 2; + alias h = (int n) => n + 3; + alias i = (int n) => n + 4; + + auto result = adjoin!(f, g, h, i)(0); + + assert(result[0] == 1); + assert(result[1] == 2); + assert(result[2] == 3); + assert(result[3] == 4); +} + /** - Composes passed-in functions $(D fun[0], fun[1], ...) returning a - function $(D f(x)) that in turn returns $(D - fun[0](fun[1](...(x)))...). Each function can be a regular - functions, a delegate, or a string. + Composes passed-in functions $(D fun[0], fun[1], ...). + + Params: + fun = the call-able(s) or `string`(s) to compose into one function + Returns: + A new function `f(x)` that in turn returns `fun[0](fun[1](...(x)))...`. See_Also: $(LREF pipe) */ template compose(fun...) +if (fun.length > 0) { static if (fun.length == 1) { alias compose = unaryFun!(fun[0]); } - else static if (fun.length == 2) + else { - // starch alias fun0 = unaryFun!(fun[0]); - alias fun1 = unaryFun!(fun[1]); + alias rest = compose!(fun[1 .. $]); - // protein: the core composition operation - typeof({ E a; return fun0(fun1(a)); }()) compose(E)(E a) + auto compose(Args...)(Args args) { - return fun0(fun1(a)); + return fun0(rest(args)); } } - else - { - // protein: assembling operations - alias compose = compose!(fun[0], compose!(fun[1 .. $])); - } } /// @@ -927,12 +1192,28 @@ template compose(fun...) assert(compose!(map!(to!(int)), split)("1 2 3").equal([1, 2, 3])); } +// https://issues.dlang.org/show_bug.cgi?id=6484 +@safe unittest +{ + int f(int a) { return a; } + int g(int a) { return a; } + int h(int a,int b,int c) { return a * b * c; } + + alias F = compose!(f,g,h); + assert(F(1,2,3) == f(g(h(1,2,3)))); +} + /** Pipes functions in sequence. Offers the same functionality as $(D compose), but with functions specified in reverse order. This may lead to more readable code in some situation because the order of execution is the same as lexical order. + Params: + fun = the call-able(s) or `string`(s) to compose into one function + Returns: + A new function `f(x)` that in turn returns `fun[$-1](...fun[1](fun[0](x)))...`. + Example: ---- @@ -946,6 +1227,7 @@ int[] a = pipe!(readText, split, map!(to!(int)))("file.txt"); */ alias pipe(fun...) = compose!(Reverse!(fun)); +/// @safe unittest { import std.conv : to; @@ -985,26 +1267,36 @@ unittest } ---- -Technically the memoized function should be pure because $(D memoize) assumes it will -always return the same result for a given tuple of arguments. However, $(D memoize) does not -enforce that because sometimes it -is useful to memoize an impure function, too. +Params: + fun = the call-able to memozie + maxSize = The maximum size of the GC buffer to hold the return values +Returns: + A new function which calls `fun` and caches its return values. + +Note: + Technically the memoized function should be pure because `memoize` assumes it will + always return the same result for a given tuple of arguments. However, `memoize` does not + enforce that because sometimes it is useful to memoize an impure function, too. */ template memoize(alias fun) { import std.traits : ReturnType; - // alias Args = Parameters!fun; // Bugzilla 13580 + // https://issues.dlang.org/show_bug.cgi?id=13580 + // alias Args = Parameters!fun; ReturnType!fun memoize(Parameters!fun args) { alias Args = Parameters!fun; import std.typecons : Tuple; + import std.traits : Unqual; - static ReturnType!fun[Tuple!Args] memo; + static Unqual!(ReturnType!fun)[Tuple!Args] memo; auto t = Tuple!Args(args); if (auto p = t in memo) return *p; - return memo[t] = fun(args); + auto r = fun(args); + memo[t] = r; + return r; } } @@ -1012,12 +1304,14 @@ template memoize(alias fun) template memoize(alias fun, uint maxSize) { import std.traits : ReturnType; - // alias Args = Parameters!fun; // Bugzilla 13580 + // https://issues.dlang.org/show_bug.cgi?id=13580 + // alias Args = Parameters!fun; ReturnType!fun memoize(Parameters!fun args) { - import std.traits : hasIndirections; + import std.meta : staticMap; + import std.traits : hasIndirections, Unqual; import std.typecons : tuple; - static struct Value { Parameters!fun args; ReturnType!fun res; } + static struct Value { staticMap!(Unqual, Parameters!fun) args; Unqual!(ReturnType!fun) res; } static Value[] memo; static size_t[] initialized; @@ -1036,7 +1330,7 @@ template memoize(alias fun, uint maxSize) } import core.bitop : bt, bts; - import std.conv : emplace; + import core.lifetime : emplace; size_t hash; foreach (ref arg; args) @@ -1046,7 +1340,9 @@ template memoize(alias fun, uint maxSize) if (!bt(initialized.ptr, idx1)) { emplace(&memo[idx1], args, fun(args)); - bts(initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025) + // only set to initialized after setting args and value + // https://issues.dlang.org/show_bug.cgi?id=14025 + bts(initialized.ptr, idx1); return memo[idx1].res; } else if (memo[idx1].args == args) @@ -1056,7 +1352,7 @@ template memoize(alias fun, uint maxSize) if (!bt(initialized.ptr, idx2)) { emplace(&memo[idx2], memo[idx1]); - bts(initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025) + bts(initialized.ptr, idx2); } else if (memo[idx2].args == args) return memo[idx2].res; @@ -1072,9 +1368,10 @@ template memoize(alias fun, uint maxSize) * To _memoize a recursive function, simply insert the memoized call in lieu of the plain recursive call. * For example, to transform the exponential-time Fibonacci implementation into a linear-time computation: */ -@safe unittest +@safe nothrow +unittest { - ulong fib(ulong n) @safe + ulong fib(ulong n) @safe nothrow { return n < 2 ? n : memoize!fib(n - 2) + memoize!fib(n - 1); } @@ -1094,8 +1391,8 @@ template memoize(alias fun, uint maxSize) } /** - * This memoizes all values of $(D fact) up to the largest argument. To only cache the final - * result, move $(D memoize) outside the function as shown below. + * This memoizes all values of `fact` up to the largest argument. To only cache the final + * result, move `memoize` outside the function as shown below. */ @safe unittest { @@ -1108,7 +1405,7 @@ template memoize(alias fun, uint maxSize) } /** - * When the $(D maxSize) parameter is specified, memoize will used + * When the `maxSize` parameter is specified, memoize will used * a fixed size hash table to limit the number of cached entries. */ @system unittest // not @safe due to memoize @@ -1154,7 +1451,7 @@ template memoize(alias fun, uint maxSize) } assert(fact(10) == 3628800); - // Issue 12568 + // https://issues.dlang.org/show_bug.cgi?id=12568 static uint len2(const string s) { // Error alias mLen2 = memoize!len2; if (s.length == 0) @@ -1169,8 +1466,9 @@ template memoize(alias fun, uint maxSize) assert(func(int.init) == 1); } -// 16079: memoize should work with arrays -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=16079 +// memoize should work with arrays +@system unittest // not @safe with -dip1000 due to memoize { int executed = 0; T median(T)(const T[] nums) { @@ -1193,7 +1491,7 @@ template memoize(alias fun, uint maxSize) assert(executed == 1); } -// 16079: memoize should work with structs +// https://issues.dlang.org/show_bug.cgi?id=16079: memoize should work with structs @safe unittest { int executed = 0; @@ -1212,9 +1510,20 @@ template memoize(alias fun, uint maxSize) assert(executed == 1); } -// 16079: memoize should work with classes +// https://issues.dlang.org/show_bug.cgi?id=20439 memoize should work with void opAssign @safe unittest { + static struct S + { + void opAssign(S) {} + } + + assert(memoize!(() => S()) == S()); +} + +// https://issues.dlang.org/show_bug.cgi?id=16079: memoize should work with classes +@system unittest // not @safe with -dip1000 due to memoize +{ int executed = 0; T pickFirst(T)(T first) { @@ -1246,6 +1555,33 @@ template memoize(alias fun, uint maxSize) assert(executed == 1); } +// https://issues.dlang.org/show_bug.cgi?id=20302 +@system unittest +{ + version (none) // TODO change `none` to `all` and fix remaining limitations + struct S { const int len; } + else + struct S { int len; } + + static string fun000( string str, S s) { return str[0 .. s.len] ~ "123"; } + static string fun001( string str, const S s) { return str[0 .. s.len] ~ "123"; } + static string fun010(const string str, S s) { return str[0 .. s.len] ~ "123"; } + static string fun011(const string str, const S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun100( string str, S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun101( string str, const S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun110(const string str, S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun111(const string str, const S s) { return str[0 .. s.len] ~ "123"; } + + static foreach (fun; AliasSeq!(fun000, fun001, fun010, fun011, fun100, fun101, fun110, fun111)) + {{ + alias mfun = memoize!fun; + assert(mfun("abcdefgh", S(3)) == "abc123"); + + alias mfun2 = memoize!(fun, 42); + assert(mfun2("asd", S(3)) == "asd123"); + }} +} + private struct DelegateFaker(F) { import std.typecons : FuncInfo, MemberFunctionGenerator; @@ -1305,6 +1641,11 @@ private struct DelegateFaker(F) * Convert a callable to a delegate with the same parameter list and * return type, avoiding heap allocations and use of auxiliary storage. * + * Params: + * fp = a function pointer or an aggregate type with `opCall` defined. + * Returns: + * A delegate with the context pointer pointing to nothing. + * * Example: * ---- * void doStuff() { @@ -1321,7 +1662,7 @@ private struct DelegateFaker(F) * * BUGS: * $(UL - * $(LI Does not work with $(D @safe) functions.) + * $(LI Does not work with `@safe` functions.) * $(LI Ignores C-style / D-style variadic arguments.) * ) */ @@ -1467,98 +1808,9 @@ if (isCallable!(F)) } } -/** -Forwards function arguments with saving ref-ness. -*/ +// forward used to be here but was moved to druntime template forward(args...) { - static if (args.length) - { - import std.algorithm.mutation : move; - - alias arg = args[0]; - static if (__traits(isRef, arg)) - alias fwd = arg; - else - @property fwd()(){ return move(arg); } - alias forward = AliasSeq!(fwd, forward!(args[1..$])); - } - else - alias forward = AliasSeq!(); -} - -/// -@safe unittest -{ - class C - { - static int foo(int n) { return 1; } - static int foo(ref int n) { return 2; } - } - int bar()(auto ref int x) { return C.foo(forward!x); } - - assert(bar(1) == 1); - int i; - assert(bar(i) == 2); -} - -/// -@safe unittest -{ - void foo(int n, ref string s) { s = null; foreach (i; 0 .. n) s ~= "Hello"; } - - // forwards all arguments which are bound to parameter tuple - void bar(Args...)(auto ref Args args) { return foo(forward!args); } - - // forwards all arguments with swapping order - void baz(Args...)(auto ref Args args) { return foo(forward!args[$/2..$], forward!args[0..$/2]); } - - string s; - bar(1, s); - assert(s == "Hello"); - baz(s, 2); - assert(s == "HelloHello"); -} - -@safe unittest -{ - auto foo(TL...)(auto ref TL args) - { - string result = ""; - foreach (i, _; args) - { - //pragma(msg, "[",i,"] ", __traits(isRef, args[i]) ? "L" : "R"); - result ~= __traits(isRef, args[i]) ? "L" : "R"; - } - return result; - } - - string bar(TL...)(auto ref TL args) - { - return foo(forward!args); - } - string baz(TL...)(auto ref TL args) - { - int x; - return foo(forward!args[3], forward!args[2], 1, forward!args[1], forward!args[0], x); - } - - struct S {} - S makeS(){ return S(); } - int n; - string s; - assert(bar(S(), makeS(), n, s) == "RRLL"); - assert(baz(S(), makeS(), n, s) == "LLRRRL"); -} - -@safe unittest -{ - ref int foo(return ref int a) { return a; } - ref int bar(Args)(auto ref Args args) - { - return foo(forward!args); - } - static assert(!__traits(compiles, { auto x1 = bar(3); })); // case of NG - int value = 3; - auto x2 = bar(value); // case of OK + import core.lifetime : fun = forward; + alias forward = fun!args; } |