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

pureEval vs a typeclass #1150

Closed
johnynek opened this issue Jun 19, 2016 · 4 comments · Fixed by #1234
Closed

pureEval vs a typeclass #1150

johnynek opened this issue Jun 19, 2016 · 4 comments · Fixed by #1234
Assignees
Milestone

Comments

@johnynek
Copy link
Contributor

Apologies if this was discussed, but I wonder why pureEval was added to Applicative vs something like:

trait ApplicativeEval[F[_]] extends Applicative[F] {
  def pureEval[A](p: Eval[A]): F[A]
}

with the law that Eq[F[A]].equiv(pureEval(e), pure(e.value))

Seems like making that always present but by default forcing .value it removes the ability of the caller of pureEval to know if value is called immediately or not. With a new type, it would not be optional laziness, it would advertise true laziness support.

Maybe this is not a super meaningful distinction (unlike tailRecM which can be implemented in a non-tailrecursive way with flatMap), but still it seemed easy enough to signal at the type level.

@ceedubs
Copy link
Contributor

ceedubs commented Jul 24, 2016

In case it's of any interest, today on Gitter @tpolecat had me questioning pureEval.

@tpolecat
Copy link
Member

What does "true laziness support" mean? Would it be a guarantee that the Eval is forced always (and only) on ap? We can't state that as an identity because it would only be observable in the presence of side-effects (including non-termination). So I take this as a hint that it's probably not a real abstraction.

@johnynek
Copy link
Contributor Author

@tpolecat yeah, I think that's right. You could test that by using a var that you increment in an Eval.Once and verify it has not been incremented after pureEval.

Is it a "real abstraction"? I don't know, but I know some M[_] do support laziness (for instance twitter's Future), which can have some implications for resource or stack safety.

That said, I am wondering if we just want to have FunctionK[Eval, M] rather than a typeclass for FromEval[M].

@adelbertc adelbertc added this to the cats-1.0.0 milestone Jul 26, 2016
@adelbertc
Copy link
Contributor

Another concern I ran into yesterday was that pureEval defaults to calling pure with eval.value - if anything I feel this should be the other way around, that pure calls pureEval with Eval.now(a).

The specific situation I ran into is writing an IO type with a MonadCatch type class. One of the laws of MonadCatch involves "throwing" an exception which relies on a lazy pure. When testing the instance for say, OptionT[IO, ?], the test failed because while IO overrode pureEval, OptionT did not and so the test failed before even having a chance to reach IO.

Some discussion on Gitter happened here: https://gitter.im/typelevel/cats?at=5796a24600c8ebdd0e239982

ceedubs added a commit to ceedubs/cats that referenced this issue Jul 27, 2016
This probably resolves typelevel#1150, though it doesn't provide an alternative
type class as was brought up there.

@adelbertc has [gotten tripped up](typelevel#1150 (comment)) by `pureEval` defaulting to a call to `pure` with `.value` called on the `Eval`.

@johnynek has also [questioned](typelevel#1150 (comment)) `pureEval` being directly on `Applicative`.

@tpolecat has [raised concerns](https://gitter.im/typelevel/cats?at=5794dc4c1b9de56c0edd887a) that `pureEval` looks like it might exist as a way to inject
side effects into a type, but that it doesn't actually make any claims
about execution semantics or value retention, so it can't be relied upon
to do this.

I think that there could be an argument for having an instantiation method for
a side-effect-y type (like a `Task`) that takes an `Eval[A]` and produces an instance
that will repeat the computation given `Always`, memoize the value given
`Later`, etc. However, I don't think that @tpolecat necessarily agrees
with me, and I think that even _if_ this is a reasonable thing to do,
`Applicative` probably isn't the place for it.

Since several people have questioned `pureEval`, it was entirely
untested, and I haven't heard any one argue for it staying, I'm inclined
to remove it. If someone has a compelling example of when a non-strict
argument can be useful for `pure`, then I may be convinced. But even
then we should clearly document what behavior can be expected of
`pureEval`.
@ceedubs ceedubs self-assigned this Jul 27, 2016
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 a pull request may close this issue.

5 participants