Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor std.algorithm.iteration #7638

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Conversation

andralex
Copy link
Member

The refactorings involve simplifying code that was working around compiler bugs, pushing symbols from top level to where they are needed, and a few more simplifications.

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @andralex!

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.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + phobos#7638"

alias _funs = AliasSeq!(_fun);
}

struct Result(Range)
Copy link
Member Author

Choose a reason for hiding this comment

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

moved from top level

@@ -955,11 +920,19 @@ private:
isForeachUnaryWithIndexIterable!R);

public:
Flag!"each" each(Range)(auto ref Range r)
if (isForeachIterable!Range ||
Copy link
Member Author

Choose a reason for hiding this comment

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

Largely unsuccessful attempt to make the constraint nicer. Will get back to this.

return Result!Range(range);
}

private struct Result(Range)
Copy link
Member Author

Choose a reason for hiding this comment

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

moved from top level

return Result!Range(r);
}

private struct Result(Range)
Copy link
Member Author

Choose a reason for hiding this comment

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

moved from top level

// Implementation of chunkBy for non-forward input ranges.
private struct ChunkByImpl(alias pred, Range)
private struct ChunkByImpl(alias pred, alias eq, bool isUnary, Range)
Copy link
Member Author

Choose a reason for hiding this comment

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

Could move this inside chunkBy as well, but it's fairly bulky.

@@ -1973,48 +1894,69 @@ if (isInputRange!Range && !isForwardRange!Range)
@property auto front()
{
assert(!empty, "Attempting to fetch the front of an empty chunkBy.");

static struct Result
Copy link
Member Author

Choose a reason for hiding this comment

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

this was small enough to plop here

}
}

void popFront()
{
assert(!empty, "Attempting to popFront an empty chunkBy.");
openChunk = false;
while (!r.empty)
for (; !r.empty; r.popFront)
Copy link
Member Author

Choose a reason for hiding this comment

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

my dream: that more loops are written like this

}
}
}

// Single-pass implementation of chunkBy for forward ranges.
private struct ChunkByImpl(alias pred, Range)
private struct ChunkByImpl(alias pred, alias eq, bool isUnary, Range)
Copy link
Member Author

Choose a reason for hiding this comment

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

this is also too bulky to move inside chunkBy

static if (isUnary)
alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b));
else
alias eq = binaryFun!pred;
Copy link
Member Author

Choose a reason for hiding this comment

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

this code was duplicated across the two implementations of ChunkByImpl.

isRangeIterable!Range ||
__traits(compiles, typeof(r.front).length)))
public Flag!"each" each(Range)(auto ref Range range)
if (isInputRange!Range || isStaticArray!Range || hasMember!(Range, "opApply"))
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a simpler condition (albeit incomplete because it doesn't verify the function). Also the entire implementation is a lot shorter.

Checked the speed and memory consumed - this version is about as fast and eats 0.1% less memory.


Returns: `Yes.each` if the entire range was iterated, `No.each` in case of early
stopping.

See_Also: $(REF tee, std,range)
*/
template each(alias fun = "a")
public Flag!"each" each(alias fun = "a", Range)(auto ref Range range)
if (isInputRange!Range || isStaticArray!Range || hasMember!(Range, "opApply"))
Copy link
Member Author

Choose a reason for hiding this comment

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

whew, nice, it can be reduced to a single function

Choose a reason for hiding this comment

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

I don't think this is something that should be celebrated.

        static if (is(typeof(f1!()))) return f1();
        else static if (is(typeof(f2!()))) return f2();
        else static if (is(typeof(f3!()))) return f3();
        else static if (is(typeof(f4!()))) return f4();
        else static if (is(typeof(f5!()))) return f5();
        else static if (is(typeof(f6!()))) return f6();
        else static if (is(typeof(f7!()))) return f7();
        else static assert(0, "Cannot iterate " ~ Range.stringof ~ " with each().");

Copy link
Member Author

Choose a reason for hiding this comment

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

The "celebration" was in regard to moving each from a member in a template to a top-level function.

I agree that the "try each pattern and see what works" strategy does raise an eyebrow, and am always on lookout for better solutions - please advise. I took inspiration from @pbackus's amazing sumtype library which works almost miraculously well at scale. There, you get to pass one or more template lambdas to a sum type and the library simply iterates all possibilities and picks the first that compiles. Experience with sumtype suggests that's a really solid idiom for D.

There are certain advantages to this strategy as implemented here compared to the existing code:

  • It's easy to understand and maintain - each pattern is in the clear. This is a very important aspect! It's very difficult to make heads and tails of the code otherwise. Once you see what patterns the library is looking for, it's very easy to get into it.
  • It's much more terse than the baseline - this PR deletes 166 lines, many of which I think are attributable to this change.
  • There's good precedent with sumtype

@andralex andralex changed the title Refactor std.algorithm.iteration through chunkBy Refactor std.algorithm.iteration Sep 19, 2020
@andralex andralex force-pushed the iteration1 branch 6 times, most recently from 1392a1c to 964a932 Compare September 20, 2020 15:53
@dukc
Copy link
Contributor

dukc commented Feb 22, 2021

Good work - just a little nicer to read all over the module. Modulo getting the test suite to pass, LGTM.

@andralex
Copy link
Member Author

whoa blast from the past

@RazvanN7
Copy link
Collaborator

@andralex what are your plans with regards to this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants