Skip to content

Commit f079a79

Browse files
authored
Change AndThen to directly check isRightAssociated (#3569)
1 parent a7fd543 commit f079a79

File tree

2 files changed

+63
-30
lines changed

2 files changed

+63
-30
lines changed

core/src/main/scala/cats/data/AndThen.scala

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,12 @@ sealed abstract class AndThen[-T, +R] extends (T => R) with Product with Seriali
7272
// technique implemented for `cats.effect.IO#map`
7373
g match {
7474
case atg: AndThen[R, A] =>
75-
(this, atg) match {
76-
case (Single(f, indexf), Single(gs, indexg)) if indexf + indexg < fusionMaxStackDepth =>
77-
Single(f.andThen(gs), indexf + indexg + 1)
78-
case (Concat(left, Single(f, indexf)), Single(gs, indexg)) if indexf + indexg < fusionMaxStackDepth =>
79-
Concat(left, Single(f.andThen(gs), indexf + indexg + 1))
80-
case _ =>
81-
Concat(this, atg)
82-
}
75+
AndThen.andThen(this, atg)
8376
case _ =>
8477
this match {
85-
case Single(f, index) if index != fusionMaxStackDepth =>
78+
case Single(f, index) if index < fusionMaxStackDepth =>
8679
Single(f.andThen(g), index + 1)
87-
case Concat(left, Single(f, index)) if index != fusionMaxStackDepth =>
80+
case Concat(left, Single(f, index)) if index < fusionMaxStackDepth =>
8881
Concat(left, Single(f.andThen(g), index + 1))
8982
case _ =>
9083
Concat(this, Single(g, 0))
@@ -95,20 +88,12 @@ sealed abstract class AndThen[-T, +R] extends (T => R) with Product with Seriali
9588
// Fusing calls up to a certain threshold, using the fusion
9689
// technique implemented for `cats.effect.IO#map`
9790
g match {
98-
case atg: AndThen[A, T] =>
99-
(this, atg) match {
100-
case (Single(f, indexf), Single(gs, indexg)) if indexf + indexg < fusionMaxStackDepth =>
101-
Single(f.compose(gs), indexf + indexg + 1)
102-
case (Concat(Single(f, indexf), right), Single(gs, indexg)) if indexf + indexg < fusionMaxStackDepth =>
103-
Concat(Single(f.compose(gs), indexf + indexg + 1), right)
104-
case _ =>
105-
Concat(atg, this)
106-
}
91+
case atg: AndThen[A, T] => AndThen.andThen(atg, this)
10792
case _ =>
10893
this match {
109-
case Single(f, index) if index != fusionMaxStackDepth =>
94+
case Single(f, index) if index < fusionMaxStackDepth =>
11095
Single(f.compose(g), index + 1)
111-
case Concat(Single(f, index), right) if index != fusionMaxStackDepth =>
96+
case Concat(Single(f, index), right) if index < fusionMaxStackDepth =>
11297
Concat(Single(f.compose(g), index + 1), right)
11398
case _ =>
11499
Concat(Single(g, 0), this)
@@ -166,15 +151,14 @@ object AndThen extends AndThenInstances0 {
166151
* Establishes the maximum stack depth when fusing `andThen` or
167152
* `compose` calls.
168153
*
169-
* The default is `128`, from which we subtract one as an optimization,
170-
* a "!=" comparison being slightly more efficient than a "<".
154+
* The default is `128`.
171155
*
172156
* This value was reached by taking into account the default stack
173157
* size as set on 32 bits or 64 bits, Linux or Windows systems,
174158
* being enough to notice performance gains, but not big enough
175159
* to be in danger of triggering a stack-overflow error.
176160
*/
177-
final private val fusionMaxStackDepth = 127
161+
final private val fusionMaxStackDepth = 128
178162

179163
/**
180164
* If you are going to call this function many times, right associating it
@@ -187,13 +171,20 @@ object AndThen extends AndThenInstances0 {
187171
// end is right associated
188172
middle match {
189173
case sm @ Single(_, _) =>
190-
val newEnd = Concat(sm, end)
174+
// here we use andThen to fuse singles below
175+
// the threshold that may have been hidden
176+
// by Concat structure previously
177+
val newEnd = AndThen.andThen(sm, end)
191178
beg match {
192-
case sb @ Single(_, _) => Concat(sb, newEnd)
193-
case Concat(begA, begB) => loop(begA, begB, newEnd, true)
179+
case sb @ Single(_, _) =>
180+
AndThen.andThen(sb, newEnd)
181+
case Concat(begA, begB) =>
182+
loop(begA, begB, newEnd, true)
194183
}
195184
case Concat(mA, mB) =>
196185
// rotate mA onto beg:
186+
// we don't need to use andThen here since we
187+
// are still preparing to put onto the end
197188
loop(Concat(beg, mA), mB, end, true)
198189
}
199190
} else {
@@ -210,6 +201,46 @@ object AndThen extends AndThenInstances0 {
210201
case Concat(Single(_, _), Single(_, _)) | Single(_, _) => fn
211202
}
212203
}
204+
205+
/**
206+
* true if this fn is already right associated, which is the faster
207+
* for calling
208+
*/
209+
@tailrec
210+
final def isRightAssociated[A, B](fn: AndThen[A, B]): Boolean =
211+
fn match {
212+
case Single(_, _) => true
213+
case Concat(Single(_, _), right) => isRightAssociated(right)
214+
case _ => false
215+
}
216+
217+
final def andThen[A, B, C](ab: AndThen[A, B], bc: AndThen[B, C]): AndThen[A, C] =
218+
ab match {
219+
case Single(f, indexf) =>
220+
bc match {
221+
case Single(g, indexg) =>
222+
if (indexf + indexg < fusionMaxStackDepth) Single(f.andThen(g), indexf + indexg + 1)
223+
else Concat(ab, bc)
224+
225+
case Concat(Single(g, indexg), right) if indexf + indexg < fusionMaxStackDepth =>
226+
Concat(Single(f.andThen(g), indexf + indexg + 1), right)
227+
228+
case _ => Concat(ab, bc)
229+
}
230+
case Concat(leftf, Single(f, indexf)) =>
231+
bc match {
232+
case Single(g, indexg) =>
233+
if (indexf + indexg < fusionMaxStackDepth) Concat(leftf, Single(f.andThen(g), indexf + indexg + 1))
234+
else Concat(ab, bc)
235+
236+
case Concat(Single(g, indexg), right) if indexf + indexg < fusionMaxStackDepth =>
237+
Concat(leftf, Concat(Single(f.andThen(g), indexf + indexg + 1), right))
238+
239+
case _ =>
240+
Concat(ab, bc)
241+
}
242+
case _ => Concat(ab, bc)
243+
}
213244
}
214245

215246
abstract private[data] class AndThenInstances0 extends AndThenInstances1 {
@@ -276,7 +307,7 @@ abstract private[data] class AndThenInstances0 extends AndThenInstances1 {
276307
AndThen(fn1.split(f, g))
277308

278309
def compose[A, B, C](f: AndThen[B, C], g: AndThen[A, B]): AndThen[A, C] =
279-
f.compose(g)
310+
AndThen.andThen(g, f)
280311
}
281312
}
282313

tests/src/test/scala/cats/tests/AndThenSuite.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,13 @@ class AndThenSuite extends CatsSuite with ScalaCheckSuite {
154154

155155
// Right associated should be identity
156156
forAll(genRight[Int]) { at =>
157-
AndThen.toRightAssociated(at) == at
157+
AndThen.isRightAssociated(AndThen.toRightAssociated(at))
158158
} &&
159159
// Left associated is never right associated
160160
forAll(genLeft[Int]) { at =>
161-
AndThen.toRightAssociated(at) != at
161+
val notInit = AndThen.isRightAssociated(at)
162+
val done = AndThen.isRightAssociated(AndThen.toRightAssociated(at))
163+
(!notInit && done)
162164
} &&
163165
// check that right associating doesn't change the function value
164166
forAll(genAndThen[Int], Gen.choose(Int.MinValue, Int.MaxValue)) { (at, i) =>

0 commit comments

Comments
 (0)