-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
override more mapAccumulate methods in Traverse #4214
Conversation
cc @BalmungSan |
@@ -65,6 +65,18 @@ trait IterableInstances { | |||
override def traverse[G[_], A, B](fa: Iterable[A])(f: A => G[B])(implicit G: Applicative[G]): G[Iterable[B]] = | |||
if (fa.isEmpty) G.pure(Iterable.empty) | |||
else G.map(Chain.traverseViaChain(toImIndexedSeq(fa))(f))(_.toVector) | |||
|
|||
override def mapAccumulate[S, A, B](init: S, fa: Iterable[A])(f: (S, A) => (S, B)): (S, Iterable[B]) = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can't use the StaticMethod implementation here because we aren't in the cats package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also no guarantee that the Iterable
would be strict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point.
override def map[A, B](fa: Map[K, A])(f: A => B): Map[K, B] = | ||
fa.map { case (k, a) => (k, f(a)) } | ||
|
||
def foldLeft[A, B](fa: Map[K, A], b: B)(f: (B, A) => B): B = | ||
fa.foldLeft(b) { case (x, (k, a)) => f(x, a) } | ||
fa.foldLeft(b) { case (x, (_, a)) => f(x, a) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clear a warning I saw.
@@ -120,6 +120,18 @@ trait SetInstances { | |||
}.value | |||
} | |||
|
|||
override def mapAccumulate[S, A, B](init: S, fa: Set[A])(f: (S, A) => (S, B)): (S, Set[B]) = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't use StaticMethods version outside of cats package.
@@ -213,7 +213,7 @@ class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A]) | |||
* Tests if some element is contained in this NonEmptyLazyList | |||
*/ | |||
final def contains(a: A)(implicit A: Eq[A]): Boolean = | |||
toLazyList.contains(a) | |||
toLazyList.exists(A.eqv(_, a)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks like a straight bug. The contains method was ignoring the A. This is technically changing behavior, but I would argue it is a bug fix.
loop(0).value | ||
} | ||
def traverse[G[_], A, B](fa: ArraySeq[A])(f: A => G[B])(implicit G: Applicative[G]): G[ArraySeq[B]] = | ||
G.map(Chain.traverseViaChain(fa)(f))(_.iterator.to(ArraySeq.untagged)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make traverse stack safe on ArraySeq. We missed this before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is eluding me a bit... How come the previous implementation was not stack safe if each its call to loop
is suspended in Eval
? Just curious, because I even tried this snippet:
val arr1 = ArraySeq.iterate(1, 100)(_ + 1)
val arr2 = arr1.traverse { a =>
println(s"\n>>> a = $a")
new Exception().printStackTrace(Console.out)
a.some
}
And seems that stack depth is kept as O(1) for the entire traverse
evaluation.
Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that work when you make 100 something like 100000?
The fn call you are printing is at constant depth but there is more to it, there is also the mapping function on the map2.
That said, we have hit many issues with traverse stack safety in the past and I think the approach used here was the same there (but maybe I am wrong).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that work when you make 100 something like 100000?
Yes, it does even for 1,000,000 :)
The fn call you are printing is at constant depth but there is more to it, there is also the mapping function on the map2.
That said, we have hit many issues with traverse stack safety in the past and I think the approach used here was the same there (but maybe I am wrong).
I mean I'm not against the new implementation (perhaps it's supposed to be more performant). Just curious, because I tried out and didn't manage to screw up the old one :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -225,7 +225,12 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable { | |||
* res2: Option[Int] = Some(123) | |||
* }}} | |||
*/ | |||
final def right: Option[B] = fold(_ => None, b => Some(b), (_, b) => Some(b)) | |||
final def right: Option[B] = | |||
this match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optimization to avoid lambdas when pattern matching will do.
def foldLeft[B, C](fa: A Ior B, b: C)(f: (C, B) => C): C = | ||
fa.foldLeft(b)(f) | ||
def foldRight[B, C](fa: A Ior B, lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = | ||
fa.foldRight(lc)(f) | ||
|
||
override def size[B](fa: A Ior B): Long = fa.fold(_ => 0L, _ => 1L, (_, _) => 1L) | ||
override def size[B](fa: A Ior B): Long = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optimization to avoid lambdas when pattern matching will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about simply if (fa.isLeft) 0L else 1L
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yikes, isLeft
and friends need to be optimized too (maybe by dynamic dispatch which is how nope, was wrong about Option
is implementedOption
).
Regardless, I still think this method would be better written in terms of isLeft
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
@@ -65,6 +65,18 @@ trait IterableInstances { | |||
override def traverse[G[_], A, B](fa: Iterable[A])(f: A => G[B])(implicit G: Applicative[G]): G[Iterable[B]] = | |||
if (fa.isEmpty) G.pure(Iterable.empty) | |||
else G.map(Chain.traverseViaChain(toImIndexedSeq(fa))(f))(_.toVector) | |||
|
|||
override def mapAccumulate[S, A, B](init: S, fa: Iterable[A])(f: (S, A) => (S, B)): (S, Iterable[B]) = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about also adding mapWithIndex
and zipWithIndex
here?
override def mapWithIndex[A, B](fa: Iterable[A])(f: (A, Int) => B): Iterable[B] =
fa.zipWithIndex.map(ai => f(ai._1, ai._2))
override def zipWithIndex[A](fa: Iterable[A]): Iterable[(A, Int)] =
fa.zipWithIndex
The motivation being they would preserve their underling class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this change but I'm suspect about the trying to add invariants we don't really promise (returning the same underlying type).
- this is already in alleycats where there are reasons to believe things are suspect. In this case Iterable is so weak it doesn't necessarily iterate in any meaningful order (otherwise it would be Seq).
- when people start to assume we offer some stronger contracts, they often wind up getting into trouble and file issues and then we are in a jam. For instance, mapWithIndex is exercising a different code path from mapAccumulate here, and if they want some other law we don't promise (like maybe they do a cast at the end because they assume the Iterable is of the same type), then we get into weird issues of explaining why we went out of our way to maintain the class, but sometimes fail to.
anyway, it's alleycats, so I added it. But I hope people won't rely on it.
@armanbilge can you review this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
override def mapAccumulate[S, A, B](init: S, fa: Map[K, A])(f: (S, A) => (S, B)): (S, Map[K, B]) = { | ||
val iter = fa.iterator | ||
var s = init | ||
val m = Map.newBuilder[K, B] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about adding the call of m.sizeHint
? I haven't a strong opinion it's required, considering extra computing of Map
length, but someone used it in cats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call. I'll update that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, LGTM!
follow up to #4209 to override I think all the remaining values for mapAccumulate.
Also did a few minor changes I noticed along the way.