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

Fix traverse_ for List and Vector to be stack safe. #3960

Merged
merged 7 commits into from
Aug 10, 2021

Conversation

johnynek
Copy link
Contributor

@johnynek johnynek commented Aug 9, 2021

This fixes traverse_ for List and Vector to avoid stack overflow errors.

This is a partial fix for #3947

@johnynek
Copy link
Contributor Author

johnynek commented Aug 9, 2021

take a look @armanbilge

@johnynek johnynek changed the title Improve mapEval somewhat in relation to #3947 Fix traverse_ for List and Vector to be stack safe. Aug 9, 2021
@johnynek johnynek requested a review from LukaJCB August 9, 2021 04:51
@armanbilge
Copy link
Member

Cool!! So, if I'm understanding correctly, this doesn't actually make it stack safe (in the theoretical sense) but more so in the practical sense, that it's effectively impossible to blow the stack due since its depth is now the logarithm of the size of the input?

@johnynek
Copy link
Contributor Author

johnynek commented Aug 9, 2021

So, I think we generally say that going log(N) depth stack is safe, since we can go 10000 deep, that means we are dealing with things on O(2^10000) before we blow the stack, which is not a size we can every actually reach.

Note, the sun does not have enough energy to enumerate all numbers to 2^200, so 2^1000 even is an unimaginably large number.

@@ -32,6 +32,12 @@ abstract class TraverseSuite[F[_]: Traverse](name: String)(implicit ArbFInt: Arb
}
}

test(s"Traverse[$name].traverseMatches_ with Option") {
forAll { (fa: F[Int], fn: Int => Option[Int]) =>
assert(Applicative[Option].void(fa.traverse(fn)) == fa.traverse_(fn))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

there is no law on this, and probably should be, but requires source and binary changes to laws.

Copy link
Member

Choose a reason for hiding this comment

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

FYI laws can be introduced without breaking either source or binary compatibility

@joroKr21
Copy link
Member

joroKr21 commented Aug 9, 2021

Maybe we need traverseViaChain_? 🤔

@johnynek
Copy link
Contributor Author

johnynek commented Aug 9, 2021

I don't think so. The point of traverseViaChain is to build back up a sequence from a Tree structure. But in the case of traverse_ we don't build a resulting sequence: we just return the Applicative G[Unit].

@joroKr21
Copy link
Member

joroKr21 commented Aug 9, 2021

Ahh right it could be just traverseIndexedSeq_ - I think it also serves to reduce code duplication so that all collections can use it.

@@ -32,6 +32,12 @@ abstract class TraverseSuite[F[_]: Traverse](name: String)(implicit ArbFInt: Arb
}
}

test(s"Traverse[$name].traverseMatches_ with Option") {
Copy link
Member

Choose a reason for hiding this comment

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

did you mean to say traverse_ matches traverse with Option?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch, thanks. Will you take a look again?

LukaJCB
LukaJCB previously approved these changes Aug 9, 2021
Copy link
Member

@LukaJCB LukaJCB left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@djspiewak djspiewak left a comment

Choose a reason for hiding this comment

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

This looks really good, but while you're in here… I would like to interest you in #3790. What you're touching right here is the bottleneck for any and all par operators, and the Eval wrapping is the most substantial cost difference between this generic implementation and a hand-optimized and specialized implementation.

@johnynek
Copy link
Contributor Author

johnynek commented Aug 9, 2021

@djspiewak I'll reply on that issue with some thoughts. I agree we are in a suboptimal situation at the moment.

Copy link
Member

@djspiewak djspiewak left a comment

Choose a reason for hiding this comment

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

I think it would be interesting to add a law, but we can hit it in a follow-up if necessary

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

Successfully merging this pull request may close these issues.

5 participants