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

Add Cont alias to ContT #3403

Merged
merged 2 commits into from
May 21, 2020
Merged

Add Cont alias to ContT #3403

merged 2 commits into from
May 21, 2020

Conversation

RaasAhsan
Copy link

Let me know if I need tests or documentation of any kind.

@@ -82,4 +82,17 @@ package object data extends ScalaVersionSpecificPackage {
def apply[S, A](f: S => A, s: S): Store[S, A] =
RepresentableStore[S => *, S, A](f, s)
}

type Cont[A, B] = ContT[Id, A, B]
Copy link
Member

Choose a reason for hiding this comment

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

I think this needs to be Eval instead of Id in order to preserve stack safety. But otherwise, big 👍 on this!

Copy link
Author

@RaasAhsan RaasAhsan May 3, 2020

Choose a reason for hiding this comment

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

So I tried to verify the stack safety of ContT[Eval, A, B]:

def add[R](a: Int, b: Int): ContT[Eval, Int, Int] =
    ContT(f => f(a + b))
val seed = add[Int](3, 2)
val f = (0 until 10000).foldLeft(seed)((acc, _) => acc.flatMap(x => Cont.pure(x + 1)))
println(f.run(x => Eval.later(x)).value)

But I seem to be getting stack overflows for this as well. Excerpt of the stack trace:

[error] java.lang.StackOverflowError
[error] 	at cats.data.ContT$.apply(ContT.scala:153)
[error] 	at cats.data.ContT$.defer(ContT.scala:132)
[error] 	at cats.data.package$Cont$.defer(package.scala:96)
[error] 	at org.simpleapp.Playground$.$anonfun$f$2(Playground.scala:11)
[error] 	at org.simpleapp.Playground$.$anonfun$f$2$adapted(Playground.scala:11)
[error] 	at scala.Function1.$anonfun$andThen$1(Function1.scala:57)
[error] 	at cats.data.AndThen.runLoop(AndThen.scala:97)
[error] 	at cats.data.AndThen.apply(AndThen.scala:67)
[error] 	at cats.data.ContT$.$anonfun$defer$1(ContT.scala:133)
[error] 	at cats.data.AndThen.runLoop(AndThen.scala:97)
[error] 	at cats.data.AndThen.apply(AndThen.scala:67)
[error] 	at cats.data.ContT.$anonfun$flatMap$2(ContT.scala:43)
[error] 	at scala.Function1.$anonfun$andThen$1(Function1.scala:57)
[error] 	at cats.data.AndThen.runLoop(AndThen.scala:97)
[error] 	at cats.data.AndThen.apply(AndThen.scala:67)
[error] 	at cats.data.ContT$.$anonfun$defer$1(ContT.scala:133)

Do you think there might be a deficiency in the ContT#flatMap implementation?

Copy link
Member

Choose a reason for hiding this comment

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

Finally got a chance to poke at this a bit. Have you tried the same test with tailRecM? I haven't looked into extreme detail about what's going on here, but I remember tailRecM being very meticulously verified as stack-safe, but I don't think the same work was done on flatMap (in fact, I remember specifically making arguments that you can't make a stack-safe flatMap for ContT).

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, no more stack overflows with tailRecM. I'll need to convince myself why it won't work for flatMap, but otherwise I think this is all ready. :) I noticed we don't have any inductive class instances for ContT in cats-effect, might take a look and see if those can be added

Copy link
Member

Choose a reason for hiding this comment

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

I'd like to add some, but I think the problem is the only one we could add in CE2 is Bracket. All Sync instances are constrained to be stack-safe in their flatMap, which obviously ContT isn't.

As an aside, the main reason why flatMap on ContT isn't stack-safe is similar to the reason why flatMap on Option isn't stack-safe: there's nothing to trampoline through. With tailRecM, you're forced to pass back to the trampoline loop with each iteration, while flatMap has no such guarantee. With Option, you need to have a recursive flatMap in order to really see the damage. With ContT, a long chain of flatMaps is recursive because it's effectively just a very long chain of andThens. Since flatMap on ContT corresponds directly to the CPS transformation, we can see it more directly in imperative form:

def direct(i: Int): String = i.toString

def cps[A](i: Int)(k: String => A): A = k(i.toString)

Now imagine chaining 10,000 cpses together. Naturally, the result will be a stack overflow.

It's worth noting that even IO would have this issue if someone worked entirely in terms of async.

@codecov-io
Copy link

codecov-io commented May 16, 2020

Codecov Report

Merging #3403 into master will decrease coverage by 0.05%.
The diff coverage is 0.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3403      +/-   ##
==========================================
- Coverage   92.70%   92.65%   -0.06%     
==========================================
  Files         379      379              
  Lines        7981     7991      +10     
  Branches      230      237       +7     
==========================================
+ Hits         7399     7404       +5     
- Misses        582      587       +5     
Flag Coverage Δ
#scala_version_212 92.68% <0.00%> (-0.06%) ⬇️
#scala_version_213 92.44% <0.00%> (-0.06%) ⬇️
Impacted Files Coverage Δ
core/src/main/scala/cats/data/AndThen.scala 94.33% <ø> (ø)
core/src/main/scala/cats/data/package.scala 57.14% <0.00%> (-31.75%) ⬇️
core/src/main/scala/cats/syntax/partialOrder.scala 100.00% <0.00%> (ø)
core/src/main/scala/cats/data/OptionT.scala 96.09% <0.00%> (+0.12%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8cb9df6...dbb6cac. Read the comment docs.

@LukaJCB LukaJCB merged commit e5fed5a into typelevel:master May 21, 2020
@travisbrown travisbrown added this to the 2.2.0-M2 milestone May 24, 2020
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.

5 participants