-
-
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
Add Defer typeclass, laws and implementations #2279
Conversation
I think this strategy can allow us to extend ContT to any cats.Monad: in the ContT.tailRecM loop, lift M to Free, use defer on Free, then finally run the Free to get back to M at the end. |
@@ -462,6 +462,12 @@ private[data] sealed abstract class IRWSTInstances extends IRWSTInstances1 { | |||
implicit def L: Monoid[L] = L0 | |||
} | |||
|
|||
implicit def catsDataDefer[F[_], E, L, SA, SB](implicit F: Defer[F]): Defer[IndexedReaderWriterStateT[F, E, L, SA, SB, ?]] = |
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.
totally trivial nit: the naming convention has a ForIRWST
suffix. Although I don't think it matters much since there is no risk of naming conflict here.
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.
yep, that was a goof up.
@@ -30,13 +30,35 @@ class FunctionSuite extends CatsSuite { | |||
import Helpers._ | |||
|
|||
checkAll("Function0[Int]", SemigroupalTests[Function0].semigroupal[Int, Int, Int]) | |||
// TODO: make an binary compatible way to do this | |||
implicit val deferFunction0: Defer[Function0] = |
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.
One way to do this that I can think of is to just write this in a new cats.instance.FunctionInstancesBinaryCompatible1
trait and then add a new cats.instances.AllInstancesBinaryCompatible1
trait to extend it. And let cats.instances.all
and cats.implicits
extend it. (basically the same way as how we extend syntax while reserving binary compatibility)
if (c <= 0) F.defer(fa(())) | ||
else F.defer(loop(c - 1)) | ||
|
||
loop(1000000) <-> (fa(())) |
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 number might be so too big that all jobs in build ran OOM.
Codecov Report
@@ Coverage Diff @@
## master #2279 +/- ##
==========================================
+ Coverage 95.08% 95.08% +<.01%
==========================================
Files 343 345 +2
Lines 5935 5987 +52
Branches 218 217 -1
==========================================
+ Hits 5643 5693 +50
- Misses 292 294 +2
Continue to review full report at Codecov.
|
@@ -435,6 +435,12 @@ private[data] abstract class IorTInstances extends IorTInstances1 { | |||
Monad[IorT[M, E, ?]] | |||
} | |||
} | |||
|
|||
implicit def catsDataDeferForIor[F[_], E](implicit F: Defer[F]): Defer[IorT[F, E, ?]] = |
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.
missing T
after catsDataDeferForIor
?
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.
looks like we are still missing the T.
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.
Why not call it EDIT: now I see that #2273's name originally mentioned a Can we rename |
@johnynek do you want this in the coming 1.2 release? |
@kailuowang I would. |
#2284 adds a FunctionInstancesBinCompat0, shall we merge that one first so that you can add your Function |
@kailuowang sounds great. |
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, thanks! 👍
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.
[deleted comment]
F.defer(fa(())) <-> fa(()) | ||
|
||
def deferDoesNotEvaluate[A](fa: Unit => F[A]): IsEq[Boolean] = { | ||
var evaluated = false |
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.
Unsuspended vars in laws make me feel uneasy. First of all I fear that such laws will not have a long life.
To fix it, you can defer the whole thing:
def deferDoesNotEvaluate[A](f: Boolean => F[A]): IsEq[Boolean] = {
val lh = F.defer {
var evaluated = false
F.defer {
evaluated = true
f(evaluated)
}
f(evaluated)
}
lh <-> f(false)
}
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’m nervous that if you pass a function that ignores the Boolean this is true trivially.
def deferIsStackSafe[A](fa: Unit => F[A]): IsEq[F[A]] = { | ||
def loop(c: Int): F[A] = | ||
if (c <= 0) F.defer(fa(())) | ||
else F.defer(loop(c - 1)) |
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 law is interesting, but in case an F[_]
provides such a powerful defer
, what about the behavior of flatMap
? Shouldn't flatMap
also be stack safe if defer
is?
Also the question on my mind would be: is defer
any relevant without a Monad
restriction? Or in other words, do we have any data type that can implement Defer
, but not Monad
?
Note that we also have a StackSafeMonad
.
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.
That's a good question about making the law stronger: if there is a monad, flatMap must be stack safe. The function cases wouldn't pass this law.
As to what might not be a monad: command line parsing is often done with an applicative, not a monad (see optparse-applicative library in haskell or https://github.com/bkirwi/decline for a cats version). Those types can still have a lawful Defer.
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.
If we have Applicative
examples without a Monad definition, I guess that's fine 👍
if (c <= 0) F.defer(fa(())) | ||
else F.defer(loop(c - 1)) | ||
|
||
val cnt = if (Platform.isJvm) 20000 else 2000 |
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.
In my experience these arbitrary values hurt for F[_]
data types that have an expensive flatMap
, for example streaming data types, because you're not talking of a single event being emitted, but multiple ones, per flatMap
.
In Cats-Effect what I did was to introduce an iterations: Int
in SyncLaws
(see sample) and an implicit Parameters
in SyncTests
(see definition and usage). Otherwise those tests would choke for Monix's Iterant
for example, especially when executed on Travis.
And in absence of that, I'd use smaller values:
val cnt = if (Platform.isJvm) 10000 else 1000
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 don’t see a compelling reason why this is only 2x too large. Can you give an idea?
@johnynek do you happen to have time to finish this one up? or can this wait until the next release? |
@kailuowang what needs to happen to merge? I didn't want to take the time to cut the counts in half suggested above. |
Do you want to respond to this feedback? Also the instance of |
@johnynek I added a commit to move |
This is failing Mima:
I'm not sure how badly we need the function instances anyway. :/ Sorry this is so much work. Unfortunately I am buried between work and travel. I'm sorry. |
@johnynek mima should've been fixed by the latest commit. It was me being dumb and totally forgot about it. |
@LukaJCB do you want to take another quick look at the commits I made? |
LGTM 👍 |
This seems like a little thing, but I was able to use it to implement
ContT[M, A, B]
, a wrapper for(B => M[A]) => M[A]
and pass all the laws using the naive map/flatMap (with cats.data.AndThen) and the following tailRecM:I think this is pretty clear that this is an interesting typeclass along with the fact that we have so many implementations.
closes #2273