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

Override memoize for Resource #3080

Merged
merged 7 commits into from
Jul 17, 2022
Merged

Conversation

armanbilge
Copy link
Member

Fixes #3079 (comment). Or so I thought 🤔

Comment on lines 1250 to 1255
override def memoize[A](fa: Resource[F, A]): Resource[F, Resource[F, A]] = {
Resource
.makeCaseFull[F, F[(A, Resource.ExitCase => F[Unit])]](poll =>
poll(F.memoize(fa.allocatedCase))) { (memo, exit) => memo.flatMap(_._2.apply(exit)) }
.map(memo => Resource.eval(memo.map(_._1)))
}
Copy link
Member Author

Choose a reason for hiding this comment

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

So one issue with this implementation is that it always forces allocation in the release step. Actually this is giving me deja vu.

Still, it seems like with more work even that could be solved.

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.

So my concern with this is it makes the semantics moderately incoherent, which in turn may have an impact on abstract code. What I'm not convinced of is that this is necessarily wrong, since the memoize semantics we inherit are consistent with the way that fiber lifecycles work, but inconsistent with the way that Resource lifecycles work. Does that make it wrong? Is the memoize implementation upstream assuming something that it shouldn't?

Basically, I feel like we need a bit more of a first-principles argument here and it's eluding me.

Comment on lines +1250 to +1258
override def memoize[A](fa: Resource[F, A]): Resource[F, Resource[F, A]] = {
Resource.eval(F.ref(false)).flatMap { allocated =>
val fa2 = F.uncancelable(poll => poll(fa.allocatedCase) <* allocated.set(true))
Resource
.makeCaseFull[F, F[(A, Resource.ExitCase => F[Unit])]](poll => poll(F.memoize(fa2)))(
(memo, exit) => allocated.get.ifM(memo.flatMap(_._2.apply(exit)), F.unit))
.map(memo => Resource.eval(memo.map(_._1)))
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I'm relatively certain this isn't enough. :-) We should make sure that all of the GenConcurrent tests for memoize are applied to this implementation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, why is that? All the existing tests in MemoizeSpec appear to work, unless I did something dumb.

@armanbilge
Copy link
Member Author

So my concern with this is it makes the semantics moderately incoherent, which in turn may have an impact on abstract code.

In what sense? Is there an example of a program you can write with only Concurrent combinators that behaves differently after this change? If so, it would definitely make me more suspicious. OTOH, if the change is only observable from use and other Resource-specific combinators, then it seems right to me.

@djspiewak djspiewak merged commit 165e3c3 into typelevel:series/3.3.x Jul 17, 2022
@armanbilge armanbilge linked an issue Jul 17, 2022 that may be closed by this pull request
This pull request was closed.
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.

Resource[F, A]#memoize releases right after first use
2 participants