Skip to content

[RFC] Make std.algorithm.searching.skipOver an eponymous template to allow partial instantiation#5576

Merged
wilzbach merged 2 commits intodlang:masterfrom
wilzbach:algo-skipover
Dec 2, 2017
Merged

[RFC] Make std.algorithm.searching.skipOver an eponymous template to allow partial instantiation#5576
wilzbach merged 2 commits intodlang:masterfrom
wilzbach:algo-skipover

Conversation

@wilzbach
Copy link
Contributor

@wilzbach wilzbach commented Jul 8, 2017

From #5497 (comment)

Argh! find does not use the eponymous template pattern, which means it's not partially instantiatable with just a pred.

It turns out that many functions in std.algorithm don't, so this is a RFC for evaluating the best way to move forward here. I picked the non-trivial function skipOver as an example.

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @wilzbach!

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

pred = The predicate that determines whether elements from each respective
range match. Defaults to equality $(D "a == b").
*/
template skipOver(alias pred = "a == b")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the eponymous pattern also used by other methods in this module (e.g. canFind, all or any).

isInputRange!R2)
{
if (r2.length > r1.length || r1[0 .. r2.length] != r2)
static if (is(typeof(pred) : string) && pred == "a == b"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am aware that @andralex isn't a huge fan of this pattern, but without this, we can't have a default pred for skipOver and thus would need to duplicate all overloads (as previously done).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If alternatives are a lot of aggravationm, then fine. But seeing template skipOver is already a sign that something has gotten too complicated...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But seeing template skipOver is already a sign that something has gotten too complicated...

Making skipOver is fully on purpose and what this entire informal RFC PR is about.

@MetaLang
Copy link
Member

MetaLang commented Jul 9, 2017

I hate this pattern too. Can't we just add another template "overload" that only takes a predicate and then forwards to another version of skipOver?

@wilzbach
Copy link
Contributor Author

I hate this pattern too. Can't we just add another template "overload" that only takes a predicate and then forwards to another version of skipOver?

How would this work without duplicating all function headers of skipOver?

@MetaLang
Copy link
Member

Yeah, true. Looks like it can't be helped.

@CyberShadow
Copy link
Member

But seeing template skipOver is already a sign that something has gotten too complicated...

Err, isn't this the by-the-book standard eponymous template pattern which allows partial instantiation and is already used in lots of places in Phobos, such as map?

@wilzbach
Copy link
Contributor Author

Btw another benefit of partial instantiation is that we get Dependency-Carrying Declarations a la DIP1005 for free :)
I wonder why DIP1005 didn't talk about this.
Consider a simple example:

template foo(alias pred = "a == b")
{
    import std.range.primitives : isInputRange;
    auto foo(R)(R r)
    if (isInputRange!R)
    {
        import std.algorithm.iteration : sum;
        return r.sum;
    }
}

void main()
{
    foo([1]); // <- remove this and no modules are imported
}
> rdmd -v foo.d
predefs   DigitalMars Posix linux ELFv1 LittleEndian D_Version2 all D_SIMD D_InlineAsm_X86_64 X86_64 CRuntime_Glibc D_LP64 D_PIC assert D_HardFloat
binary    /usr/bin/dmd
version   v2.074.0
config    /etc/dmd.conf
parse     foo
importall foo
import    object	(/usr/include/dlang/dmd/object.d)
semantic  foo
entry     main      	foo.d
semantic2 foo
semantic3 foo
import    std.range.primitives	(/usr/include/dlang/dmd/std/range/primitives.d)
import    std.traits	(/usr/include/dlang/dmd/std/traits.d)
import    std.typetuple	(/usr/include/dlang/dmd/std/typetuple.d)
import    std.meta	(/usr/include/dlang/dmd/std/meta.d)
import    std.algorithm.iteration	(/usr/include/dlang/dmd/std/algorithm/iteration.d)
import    std.functional	(/usr/include/dlang/dmd/std/functional.d)
import    std.ascii	(/usr/include/dlang/dmd/std/ascii.d)
import    std.algorithm.internal	(/usr/include/dlang/dmd/std/algorithm/internal.d)
code      foo
function  D main
function  std.range.primitives.front!int.front
function  foo.foo!().foo!(int[]).foo
function  std.algorithm.iteration.sum!(int[]).sum
function  std.algorithm.iteration.sum!(int[], int).sum
function  std.algorithm.iteration.reduce!"a + b".reduce!(int, int[]).reduce
function  std.algorithm.iteration.reduce!"a + b".reducePreImpl!(int[], int).reducePreImpl
function  std.algorithm.iteration.reduce!"a + b".reduceImpl!(false, int[], int).reduceImpl
function  std.functional.F!("a + b", "a", "b").binaryFun!(int, int).binaryFun
cc /tmp/.rdmd-1000/rdmd-foo.d-14302BAA00C11879E03D4743294EEEA6/objs/foo.o -o /tmp/.rdmd-1000/rdmd-foo.d-14302BAA00C11879E03D4743294EEEA6/foo.tmp -m64 -L/usr/lib -Xlinker --export-dynamic -Xlinker -Bstatic -lphobos2 -Xlinker -Bdynamic -lpthread -lm -lrt -ldl 

Now without the use of the eponymous template:

> rdmd -v foo.d
predefs   DigitalMars Posix linux ELFv1 LittleEndian D_Version2 all D_SIMD D_InlineAsm_X86_64 X86_64 CRuntime_Glibc D_LP64 D_PIC assert D_HardFloat
binary    /usr/bin/dmd
version   v2.074.0
config    /etc/dmd.conf
parse     foo
importall foo
import    object	(/usr/include/dlang/dmd/object.d)
semantic  foo
entry     main      	foo.d
semantic2 foo
semantic3 foo
code      foo
function  D main
cc /tmp/.rdmd-1000/rdmd-foo.d-14302BAA00C11879E03D4743294EEEA6/objs/foo.o -o /tmp/.rdmd-1000/rdmd-foo.d-14302BAA00C11879E03D4743294EEEA6/foo.tmp -m64 -L/usr/lib -Xlinker --export-dynamic -Xlinker -Bstatic -lphobos2 -Xlinker -Bdynamic -lpthread -lm -lrt -ldl 

@wilzbach wilzbach requested a review from andralex July 17, 2017 17:31
@wilzbach
Copy link
Contributor Author

@andralex so what's your opinion on this? Can we move forward?

@andralex
Copy link
Member

andralex commented Jul 25, 2017

So the current crop of skipOver overloads is:

bool skipOver(R1, R2)(ref R1 r1, R2 r2)
if (isForwardRange!R1
	&& isInputRange!R2
    && is(typeof(r1.front == r2.front)));

bool skipOver(alias pred, R1, R2)(ref R1 r1, R2 r2)
if (isForwardRange!R1
	&& isInputRange!R2
    && is(typeof(binaryFun!pred(r1.front, r2.front))));

bool skipOver(alias pred, R)(ref R r1)
if (isForwardRange!R
	&& ifTestable!(typeof(r1.front), unaryFun!pred));

bool skipOver(R, E)(ref R r, E e)
if (isInputRange!R && is(typeof(r.front == e) : bool));

bool skipOver(alias pred, R, E)(ref R r, E e)		
if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R);

My first notes independent on this PR:

  • There are a few mistakes - the type of == and predicate must be if-testable. Only one overload gets that right!
  • It seems the constraint on the third overload is too tight - the range can be input, not forward
  • There are a few forms of duplication in the constraints:
    • equality or predicates should always work the same
    • in the one-range version the range must be input
    • in the two-range version the first must be forward and the second must be input

Based on the above I'd say this PR may be worth it even if we discount for the advantages of partial instantiation and import.

I'll now make a pass through the code looking for details.

Copy link
Member

@andralex andralex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I'm fine with this. Before we open the floodgates to reworking everything this way, if this pattern is too frequent we may be better off with a small language addition (something like allowing overloading on template alias comes to mind).

$(D r1) is left in its original position.
*/
bool skipOver(R1, R2)(ref R1 r1, R2 r2)
if (is(typeof(binaryFun!pred(r1.front, r2.front))) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be ifTestable (not related to this PR but can go with it because code disobeying that doesn't compile anyway)


/// Ditto
bool skipOver(R, E)(ref R r, E e)
if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

predicate should be ifTestable

@andralex
Copy link
Member

ping @wilzbach this is approved with a couple of nits, feel free to amend or commit as is

@wilzbach
Copy link
Contributor Author

ping @wilzbach this is approved with a couple of nits, feel free to amend or commit as is

Thanks for the ping and I am well aware of it, but I have been pretty busy with life for the last days :/
Though I hopefully will get back to this soon :)

@wilzbach
Copy link
Contributor Author

this is approved with a couple of nits, feel free to amend or commit as is

Okay I put this on the auto-merge queue now. IfTestable is only used in Phobos 10 times, so imho it's better to do these changes in a separate PR.

@WalterBright
Copy link
Member

I turned off automerge because it was turned on for 6 days and did not merge due to failure(s) in CyberShadow/DAutoTest

@WalterBright
Copy link
Member

ping @wilzbach

@wilzbach
Copy link
Contributor Author

wilzbach commented Dec 2, 2017

Merging manually as there seems to be something wrong with the CI status report from the auto-tester (probably it has sent more than 1000 status updates):

image

It's 100% green:

image

image

@wilzbach wilzbach merged commit 60fba57 into dlang:master Dec 2, 2017
@wilzbach wilzbach deleted the algo-skipover branch December 2, 2017 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants