Skip to content

Conversation

@acehreli
Copy link
Contributor

Implement TaskPool.fold similar to std.algorithm.fold: Seeds come after the range and need not be a tuple.

Plus a couple of link corrections in std.algorithm documentation.

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @acehreli! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.

Some tips to help speed things up:

  • smaller, focused PRs are easier to review than big ones

  • try not to mix up refactoring or style changes with bug fixes or feature enhancements

  • provide helpful commit messages explaining the rationale behind each change

Bear in mind that large or tricky changes may require multiple rounds of review and revision.

Please see CONTRIBUTING.md for more information.

Bugzilla references

Auto-close Bugzilla Description
18096 Add fold() to std.parallelism

Copy link
Contributor

@wilzbach wilzbach left a comment

Choose a reason for hiding this comment

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

Congratulations on your first PR to Phobos, Ali!
We are trying to make Phobos code and documentation look modern, so there are a few things I commented on.

///
unittest
{
auto result = taskPool.fold!"a+b"([1, 2, 3, 4]);
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. Using string as a function isn't the recommended style anymore. Please use a real lambda function for the first example. So e.g.
fold!((a,b) => a +b)([1, 2, 3, 4]);
...
fold!"a*b"([1, 2, 3, 4]);
  1. This needs StdUnittest otherwise the unittest will be instantiated in user code. See the linked PR for details.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then I'm inclined to fix TaskPool.reduce as well. Would it be appropriate in this branch?

Copy link
Contributor

Choose a reason for hiding this comment

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

As long as it's a separate commit, it's not a problem ;-)

Copy link
Contributor

Choose a reason for hiding this comment

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

Btw StdUnittest has been merged, so once you rebase to upstream/master, you can use it 😉

auto result = taskPool.fold!("a+b", "a*b")([1, 2, 3, 4], 0, 1);
assert(result[0] == 10);
assert(result[1] == 24);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs StdUnittest

assert(y[1] == 24);
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

You will need to add unittest here, so that your template is instantiated at least once and the unittest within it are run.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. And making sure the unittests run exposed the infamous

"Error: template instance fold!(function (a, b) => a + b) cannot use local '__funcliteral1' as parameter to non-global template fold(functions...)"

which means, I have to pull all unittests out to the module scope, which means, I can't take advantage of automatically inserting unittests as examples right under what they test. I think I will have to copy the code under Examples sections as had been done for TaskPool.reduce().

Any ideas for a solution?

Copy link
Member

Choose a reason for hiding this comment

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

The usual practice is to put the unittest block(s) right after the template declaration. This way the examples will still get inserted into the docs for the template. Which is fine in this case, because it's not a big deal to just leave the inner template with a blank ///, since the bulk of the docs will be on the outer template.

Furthermore, putting unittests inside a template is generally not a wise idea in terms of code bloat / possibly compilation time, because they will get instantiated for every instantiation of the template. Somebody tried to propose a static unittest sometime ago but it was shot down by Walter. Maybe we could try changing Walter's opinion on this again? :-P But in the meantime, keeping unittests in module scope seems to be the best compromise.

Copy link
Member

Choose a reason for hiding this comment

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

P.S. There's the workaround of using static if on a unittest inside a template so that it only gets instantiated for a specific template instantiation (i.e., only once), but that in turn requires something outside the template to trigger that specific instantiation in the first place, which makes it rather fragile.

Copy link
Contributor

Choose a reason for hiding this comment

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

Furthermore, putting unittests inside a template is generally not a wise idea in terms of code bloat / possibly compilation time, because they will get instantiated for every instantiation of the template.

FYI @quickfur we have StdUnittest for this purpose now (see #5927).

format("Invalid number of arguments (%s): Should be an input range, %s optional seed(s)," ~
" and an optional work unit size", Args.length, functions.length));
return fold(args[0], args[1..$]);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes that's the preferred Phobos style. It gives good error messages to people and doesn't hit them with a long list of overloads.
=> The other overloads should be private.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alas, they disappear from documentation if they're made private.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that's the idea ;-)

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 I want to keep the internal fold functions and their unittests in the documentations. (reduce has the similar template structure but it does not use nested unittests:

template fold(functions...)
{
    // The outer template is almost useless as it's only for 'functions...'. The three different uses of Args... come next:
        auto fold(R)(R range)
        {
            return reduce!functions(range);
        }
    version (StdUnittest)
    unittest {
        // for this overload
    }
//... etc
}

So, that's why the nested ones cannot be private and I really want to keep the unittest right under their respective overloads.

$(LREF fold) is functionally equivalent to $(LREF _reduce) except the
range parameter comes first and there is no need to use
$(REF_ALTTEXT $(D tuple),tuple,std,typecons) for multiple seeds.
Copy link
Contributor

Choose a reason for hiding this comment

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

Refs automatically use code boxes. If they don't we should fix it for all of them.
Also Phobos prefers backticks nowadays.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review. Yeah, the font was regular text font without $(D). I'll replace with backticks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thinking more about this, none of the _ALTTEXT macros should use code boxes as the alternative text can be any free form text. I've just checked dlang.org.ddoc and these macros look correct to me. So, back to backticks...

The accumulated result as a single value for single function and as a tuple of values for multiple functions
See_Also:
$(LREF reduce)
Copy link
Contributor

Choose a reason for hiding this comment

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

std.algorithm.iteration.fold?

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 really meant TaskPool.reduce here because std.algorithm.iteration.fold does not have the peculiarities of this module (mostly about the seeds).

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I meant: how about mentioning std.algorithm.iteration.fold too? (sorry for the brevity, I'm on my phone).
Btw I think it would be better to use TaskPool.reduce as LREF here, but from what I can see there's only MYREF, but no LREF_ALTTEXT yet :/

Also at least for Ddoc the link would be #.TaskPool.reduce, so maybe the ugly MYREF works already?

}

/** Implements the homonym function (also known as $(D accumulate), $(D
compress), $(D inject), or $(D foldl)) present in various programming
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Phobos code prefers backticks, see e.g. #5801

This is functionally equivalent to $(LREF reduce) except the range
parameter comes first and there is no need to use $(REF_ALTTEXT
$(D tuple),tuple,std,typecons) for multiple seeds.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: backticks

@acehreli
Copy link
Contributor Author

acehreli commented Dec 25, 2017

Here's one more iteration. To repeat myself, I like the inline unittest blocks as they give examples right under each overload.

Regarding string functions, they have a benefit over lambdas: We can't use lambdas with member function templates with the current implementation limitations (not enough context pointers). That's why I used static nested functions. I don't like it but I don't have a better option.

Thank you...

@acehreli
Copy link
Contributor Author

Another problem with lambdas as opposed to string lambdas: dmd runs out of memory. :( (That's why the auto-tester failed. Oy!

@acehreli
Copy link
Contributor Author

I had issues related to unittests. As one of the code comments explains, either the test was showing up at the wrong spot or it would cause dmd to run out of memory.

In the end, I had to do what reduce does: Hand-written Example section, which is unfortunate because these examples can't be edited or run by the users.

Copy link
Contributor

@wilzbach wilzbach left a comment

Choose a reason for hiding this comment

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

This is still missing a changelog entry and as all new public symbols it requires @andralex's approval.

auto range()
{
return args[0];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not alias range = args[0];?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sweet! I thought it would be rejected as args[0] is an expression.

}
else
{
static assert(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs an error message or simply use else // static if like e.g. here:

else // if (isRangeBinaryIterable!Range)

// they would appear under the outer one. (We can't move this inside the
// outer fold() template because then dmd runs out of memory possibly due to
// recursive template instantiation, which is surprisingly not caught.)
version(StdUnittest)
Copy link
Contributor

Choose a reason for hiding this comment

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

version(StdUnittest) is only needed in templates - this isn't one anymore.

version(StdUnittest)
@system unittest
{
static int adder(int a, int b)
Copy link
Contributor

Choose a reason for hiding this comment

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

Argh we should really fix 5710.

// The range, the seed (0), and the work unit size (20)
auto z = taskPool.fold!adder([1, 2, 3, 4], 0, 20);
assert(z == 10);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: usually we try to have an empty line before a new declaration.

}
}

version(StdUnittest)
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove StdUnittest - it's as a workaround for the non-existing DIP82 only used for templates to avoid unittest block leakage into user code.

static assert(args.length == 1 || // just the range
args.length == 1 + functions.length || // range and seeds
args.length == 1 + functions.length + 1, // range, seeds, and workUnitSize
format("Invalid number of arguments (%s): Should be an input range, %s optional seed(s)," ~
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be "%d" clarity. Also using std.format at CTFE is really expensive.

// The range, the seed (0), and the work unit size (20)
auto z = taskPool.fold!adder([1, 2, 3, 4], 0, 20);
assert(z == 10);
---
Copy link
Contributor

Choose a reason for hiding this comment

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

Obviously not a huge fan of this either, but fixing this is independent to this addition as reduce is equally affected...

@acehreli
Copy link
Contributor Author

Once more... Hoping that the out-of-memory auto-test errors were due to proper lambdas. This commit uses string lambdas (only in non-documentation unittests).

@vinobheeman
Copy link

Hi All Approvers,

This pull request is still waiting for few of your approvals, so request your approval on the same.

From
Vino.B

@acehreli
Copy link
Contributor Author

acehreli commented Jan 5, 2018

Is there anything else that I should do for this? Thanks...

@quickfur
Copy link
Member

quickfur commented Jan 5, 2018

Adding new public symbols to Phobos requires @andralex's approval, so we need to somehow get his attention in order to move this forward.

@wilzbach
Copy link
Contributor

Is there anything else that I should do for this? Thanks...

Yeah we really need to improve the status quo. Way too many @andralex PRs sink into the abyss :/

@quickfur
Copy link
Member

I'm tempted, but haven't actually dared, to just merge it anyway and let @andralex push the Revert button afterwards if he doesn't approve. But that might be stepping over the line. :-P

@quickfur
Copy link
Member

TBH, I think it's time to expand the @WalterBright + @andralex duo to include a few more trusted delegates that can make the less important decisions on their behalf. Requiring, e.g., @andralex's approval for every tiny public symbol added to Phobos, while understandable, is just not scalable. He gets burned out needing to review tons of relatively small changes (big-picture-wise speaking), and the rest of us get frustrated 'cos the queue is piling up with no end in sight. Lose-lose situation.

If he were to delegate, say, certain subsets of Phobos modules to certain people he trusts, that would help move things along at a more reasonable pace. To some extent this is already true for certain focused modules, like @jmdavis being the go-to person for std.datetime, or @DmitryOlshansky for std.uni, etc.. But Phobos is big, and we need more delegates to look after important parts like std.range, std.algorithm, just to name a few.

@andralex
Copy link
Member

@quickfur already doing a lot of that... it's the swelling of PRs that's difficult to keep up, not the addition of names.

@andralex
Copy link
Member

I'm tempted, but haven't actually dared, to just merge it anyway and let @andralex push the Revert button afterwards if he doesn't approve. But that might be stepping over the line. :-P

I wouldn't take offense with that, the only problem is it did happen to me in the past to leave it to others, then look over stuff after a while and see that matters took a definite turn for the worse - even as admitted by the authors! Please continue to keep me on the hook for new additions, thanks!

@dlang-bot dlang-bot merged commit fa0a619 into dlang:master Jan 17, 2018
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