Skip to content

impl FromIterator<()> for ()#45379

Merged
bors merged 1 commit into
rust-lang:masterfrom
cuviper:unit_from_iter
Nov 8, 2017
Merged

impl FromIterator<()> for ()#45379
bors merged 1 commit into
rust-lang:masterfrom
cuviper:unit_from_iter

Conversation

@cuviper

@cuviper cuviper commented Oct 19, 2017

Copy link
Copy Markdown
Member

This just collapses all unit items from an iterator into one. This is
more useful when combined with higher-level abstractions, like
collecting to a Result<(), E> where you only care about errors:

use std::io::*;
data = vec![1, 2, 3, 4, 5];
let res: Result<()> = data.iter()
    .map(|x| writeln!(stdout(), "{}", x))
    .collect();
assert!(res.is_ok());

This just collapses all unit items from an iterator into one.  This is
more useful when combined with higher-level abstractions, like
collecting to a `Result<(), E>` where you only care about errors:

```rust
use std::io::*;
data = vec![1, 2, 3, 4, 5];
let res: Result<()> = data.iter()
    .map(|x| writeln!(stdout(), "{}", x))
    .collect();
assert!(res.is_ok());
```
@rust-highfive

Copy link
Copy Markdown
Contributor

r? @alexcrichton

(rust_highfive has picked a reviewer for you, use r? to override)

@cuviper

cuviper commented Oct 19, 2017

Copy link
Copy Markdown
Member Author

I also thought about letting this consume any Item type, like a >/dev/null analogy. But I think that may be too surprising if someone tries to collect real items when accidentally in a context that infers () type, as this is often implicit.

@cuviper

cuviper commented Oct 19, 2017

Copy link
Copy Markdown
Member Author

This is related to #44546 too, whether or not it might take any Item.

@kennytm kennytm added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Oct 19, 2017
@alexcrichton

Copy link
Copy Markdown
Member

@rfcbot fcp merge

Seems like a nifty idea to me!

@rfcbot

rfcbot commented Oct 19, 2017

Copy link
Copy Markdown

Team member @alexcrichton has proposed to merge this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Oct 19, 2017
@oli-obk

oli-obk commented Oct 20, 2017

Copy link
Copy Markdown
Contributor

I consider this an antipattern. I mean we have for_each so noone has to write map(...).count() or other weird combinations. I see the use of this change for generic code, but regular code should be using for_each. Together with this change we can change for_each backwards compatibly to

    fn for_each<F, R, R2>(self, f: F) -> R where
        Self: Sized,
        F: FnMut(Self::Item) -> R2, 
        R: FromIterator<R2>,
    {
        self.map(f).collect()
    }

Both changes together I can get behind. But it feels inconsistent if we consider code idiomatic that does map + collect and throws away a unit value, but not idiomatic if it's map + count and throwing away the count.

@cuviper

cuviper commented Oct 20, 2017

Copy link
Copy Markdown
Member Author

@oli-obk On it's own, I would agree this is pretty useless, and I would encourage using for_each more, or to continue using map(_).collect() for non-unit collections. I only like this new impl for cases that specifically need FromIterator, like the example with Result's FromIterator.

But it feels inconsistent if we consider code idiomatic that does map + collect and throws away a unit value, but not idiomatic if it's map + count and throwing away the count.

I would not encourage map + collect to a unit, but even so throwing away a unit is a utterly trivial -- it's nothing, and costs nothing to create. A count requires actual computation, and hopefully the optimizer will prune that if you throw it away, but otherwise it's wasted work.

@scottmcm

Copy link
Copy Markdown
Member

This feels like a fold to me, not a collect. Perhaps this needs a different method?

If we had try_fold (see fold_ok internal iteration thread), say, then it'd just be .try_fold((), |(),x| x) (play demo), which I find much more obvious, though of course it's somewhat longer.

@cuviper

cuviper commented Oct 21, 2017

Copy link
Copy Markdown
Member Author

try_fold does look more general -- it could even write the example without map:

let res: Result<()> = data.iter()
    .try_fold((), |(),x| writeln!(stdout(), "{}", x));

@petrochenkov

petrochenkov commented Oct 21, 2017

Copy link
Copy Markdown
Contributor

IntoIterator can be implemented for tuples of other IntoIterators - rust-lang/rfcs#930 (which would be a more useful extension, IMO) and there's a blanket impl of FromIterator for IntoIterators.
Any chances that impls from this PR and rust-lang/rfcs#930 will conflict or behave inconsistently?

EDIT: Disregard this, I shouldn't review in hurry.

@cuviper

cuviper commented Oct 21, 2017

Copy link
Copy Markdown
Member Author

there's a blanket impl of FromIterator for IntoIterators

I'm not sure what you're referring to. There's a blanket impl of IntoIterator for any Iterator, but FromIterator is on the consumption side.

@cuviper

cuviper commented Oct 25, 2017

Copy link
Copy Markdown
Member Author

@scottmcm Have you proposed try_fold in a PR? As a possible enticement, I've also prototyped similar methods for rayon. (it's a bit more involved... 😅)

@scottmcm

Copy link
Copy Markdown
Member

@cuviper I'm working on it 😄 (Got ahead of myself and broke something bad enough that the tests won't tell me what, though, so I need to split it up into some simpler changes. But I'm not in too much of a rush, because it'll only be full goodness once #45225 fixes #43278.)

@cuviper

cuviper commented Oct 25, 2017

Copy link
Copy Markdown
Member Author

Nice, that's very thorough!

@scottmcm

scottmcm commented Oct 29, 2017

Copy link
Copy Markdown
Member

Well, the try_fold PR is up: #45595

Looking at cuviper's example again, that's of course the same pattern as for_each in terms of fold. That coupled with oli-obk's mention of for_each makes me wonder if this instead ought to be

fn try_for_each<F, R>(&mut self, mut f: F)
    where Self: Sized, F: FnMut(Self::Item) -> R, R: Try<Ok=()>
{
    self.try_fold((), move |(), x| f(x))
}

(With, like for_each, no need for an r version thanks to #44682-style forwarding.)

Edit: That pattern ((), |(),x|) is also how my PR implements all and any, so having try_for_each would simplify them as well, further making me think it would be a useful addition.

@cuviper

cuviper commented Nov 2, 2017

Copy link
Copy Markdown
Member Author

@scottmcm you're shaving away all the motivation here... and that's OK! 😄

Are there any other higher-level abstractions that would still be useful with this FromIterator? I thought perhaps unzip, but that actually uses Extend, and I can't think of a good motivation for that anyway.

@rfcbot

rfcbot commented Nov 7, 2017

Copy link
Copy Markdown

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Nov 7, 2017
@alexcrichton

Copy link
Copy Markdown
Member

So at this point the libs team have all signed off on merging this, but @cuviper just to confirm, you're still ok with merging this?

@cuviper

cuviper commented Nov 7, 2017

Copy link
Copy Markdown
Member Author

I'm still OK with this. I like the generality of the Try-based stuff, but I think there's still a basic niche occupied here as a building block.

@alexcrichton

Copy link
Copy Markdown
Member

Ok!

@bors: r+

@bors

bors commented Nov 7, 2017

Copy link
Copy Markdown
Collaborator

📌 Commit 68d05b2 has been approved by alexcrichton

@bors

bors commented Nov 8, 2017

Copy link
Copy Markdown
Collaborator

⌛ Testing commit 68d05b2 with merge e177df3...

bors added a commit that referenced this pull request Nov 8, 2017
impl FromIterator<()> for ()

This just collapses all unit items from an iterator into one.  This is
more useful when combined with higher-level abstractions, like
collecting to a `Result<(), E>` where you only care about errors:

```rust
use std::io::*;
data = vec![1, 2, 3, 4, 5];
let res: Result<()> = data.iter()
    .map(|x| writeln!(stdout(), "{}", x))
    .collect();
assert!(res.is_ok());
```
@bors

bors commented Nov 8, 2017

Copy link
Copy Markdown
Collaborator

☀️ Test successful - status-appveyor, status-travis
Approved by: alexcrichton
Pushing e177df3 to master...

@bors bors merged commit 68d05b2 into rust-lang:master Nov 8, 2017
@kennytm kennytm added the relnotes Marks issues that should be documented in the release notes of the next release. label Nov 15, 2017
bors added a commit that referenced this pull request Nov 17, 2017
Short-circuiting internal iteration with Iterator::try_fold & try_rfold

These are the core methods in terms of which the other methods (`fold`, `all`, `any`, `find`, `position`, `nth`, ...) can be implemented, allowing Iterator implementors to get the full goodness of internal iteration by only overriding one method (per direction).

Based off the `Try` trait, so works with both `Result` and `Option` (:tada: #42526).  The `try_fold` rustdoc examples use `Option` and the `try_rfold` ones use `Result`.

AKA continuing in the vein of PRs #44682 & #44856 for more of `Iterator`.

New bench following the pattern from the latter of those:
```
test iter::bench_take_while_chain_ref_sum          ... bench:   1,130,843 ns/iter (+/- 25,110)
test iter::bench_take_while_chain_sum              ... bench:     362,530 ns/iter (+/- 391)
```

I also ran the benches without the `fold` & `rfold` overrides to test their new default impls, with basically no change.  I left them there, though, to take advantage of existing overrides and because `AlwaysOk` has some sub-optimality due to #43278 (which 45225 should fix).

If you're wondering why there are three type parameters, see issue #45462

Thanks for @bluss for the [original IRLO thread](https://internals.rust-lang.org/t/pre-rfc-fold-ok-is-composable-internal-iteration/4434) and the rfold PR and to @cuviper for adding so many folds, [encouraging me](#45379 (comment)) to make this PR, and finding a catastrophic bug in a pre-review.
bors Bot added a commit to rayon-rs/rayon that referenced this pull request Jan 14, 2018
497: impl FromParallelIterator<()> for () r=cuviper a=cuviper

This is more useful when combined with higher-level abstractions, like
collecting to a `Result<(), E>` where you only care about errors.

This is a parallel version of rust-lang/rust#45379.
Cc #496

498: FromParallelIterator and ParallelExtend Cow for String r=cuviper a=cuviper

Parallel version of rust-lang/rust#41449.
bors Bot added a commit to rayon-rs/rayon that referenced this pull request Jan 14, 2018
497: impl FromParallelIterator<()> for () r=cuviper a=cuviper

This is more useful when combined with higher-level abstractions, like
collecting to a `Result<(), E>` where you only care about errors.

This is a parallel version of rust-lang/rust#45379.
Cc #496
bors Bot added a commit to rayon-rs/rayon that referenced this pull request Jan 14, 2018
497: impl FromParallelIterator<()> for () r=cuviper a=cuviper

This is more useful when combined with higher-level abstractions, like
collecting to a `Result<(), E>` where you only care about errors.

This is a parallel version of rust-lang/rust#45379.
Cc #496
@cuviper cuviper deleted the unit_from_iter branch January 26, 2018 21:28
Manishearth added a commit to Manishearth/rust that referenced this pull request Feb 19, 2018
Add Iterator::try_for_each

The fallible version of `for_each` aka the stateless version of `try_fold`.  Inspired by @cuviper's comment in rust-lang#45379 (comment) as a more direct and obvious solution than `.map(f).collect::<Result<(), _>>()`.

Like `for_each`, no need for an `r` version thanks to overrides in `Rev`.

`iterator_try_fold` tracking issue: rust-lang#45594
Manishearth added a commit to Manishearth/rust that referenced this pull request Feb 23, 2018
Add Iterator::try_for_each

The fallible version of `for_each` aka the stateless version of `try_fold`.  Inspired by @cuviper's comment in rust-lang#45379 (comment) as a more direct and obvious solution than `.map(f).collect::<Result<(), _>>()`.

Like `for_each`, no need for an `r` version thanks to overrides in `Rev`.

`iterator_try_fold` tracking issue: rust-lang#45594
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants