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

Implement CanFail #2049

Merged
merged 16 commits into from Oct 31, 2019
Merged

Implement CanFail #2049

merged 16 commits into from Oct 31, 2019

Conversation

adamgfraser
Copy link
Contributor

Resolves #2047.

Adds a new implicit instance CanFail[E] which provides implicit evidence that an effect with an error type E can fail, that is that E is not equal to Nothing. Requires such evidence to exist for ZIO combinators such as orElse that only make sense if an effect can fail.

Not implemented yet is adding annotations so that the implicit type does not appear in the Scaladoc. It looks like this has to be done through the @useCase annotation, but one watch out there is it requires you to specify the "simplified" type signature as a text string, so there is some risk that the documentation gets out of sync with the actual type signature since it is not tied to the code.

Also not implemented is applying this technique to other effect types such as ZManaged and ZStream.

mschuwalow
mschuwalow previously approved these changes Oct 28, 2019
core/shared/src/main/scala/zio/ZIO.scala Outdated Show resolved Hide resolved
core/shared/src/main/scala/zio/ZIO.scala Outdated Show resolved Hide resolved
Copy link
Contributor

@vasilmkd vasilmkd left a comment

Choose a reason for hiding this comment

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

Should we add this to ZSink and ZStream combinators as well?

@adamgfraser
Copy link
Contributor Author

@vasilmkd Yes definitely.

@neko-kai
Copy link
Member

neko-kai commented Oct 29, 2019

@adamgfraser
What do you think about adding tests that the combinators actually fail when called with Nothing?

  • For Scala 2 you can use shapeless.test.illTyped in test scope
  • Dotty apparently has a builtin scala.compiletime.testing.typeChecks function - example

Importantly, behavior of ambiguous implicits changed in Dotty, so I'm not sure the same approach would work there...

@adamgfraser
Copy link
Contributor Author

@neko-kai Yes, we should definitely test this. I also wonder if we should add this functionality to ZIO Test.

You’re right about Dotty. We need to use a slight variation of this technique to work across all platforms. I’ll push a commit to fix that shortly.

@jdegoes
Copy link
Member

jdegoes commented Oct 29, 2019

@neko-kai @adamgfraser Please no dependency on Shapeless. If we do anything for this, it should be possible to support Scala 2.x and 3.x simultaneously.

@adamgfraser
Copy link
Contributor Author

adamgfraser commented Oct 29, 2019

Okay, so to summarize I rolled back fold and foldM.

Open questions / next steps:

  1. Making similar changes to other ZIO data types.
  2. Do we want to use the @useCase annotation for these? Personally I am somewhat inclined to leave it as is. First, the name CanFail now makes it very clear what it is, thanks to your good suggestion to use a more specific type constraint, and we can turn it into a feature. Second, it will make sure the type signature stays in sync for any changes to these methods. Third, it avoids the "that isn't actually the type signature" critique that I've seen applied to other uses of @useCase. But happy to do whatever you think is best.
  3. Testing - what do you think about pursuing a separate PR to add some "does not compile" functionality to ZIO Test. We would have to do some platform specific work for Scala 2 versus Dotty but I think we could do all of that on the backend and expose a consistent API across platforms that the entire ZIO ecosystem could use so they didn't have to rely on third part libraries. ScalaTest offers this functionality. To be clear I am envisioning this not requiring any dependencies.

@adamgfraser
Copy link
Contributor Author

It may not be worth it but we could potentially do the same thing with the R type (e.g. it doesn't make sense to call provide on an effect that does not require any environment).

@vasilmkd
Copy link
Contributor

Of course it's worth it. ZIO is a next-generation effect system after all. 💪

@jdegoes
Copy link
Member

jdegoes commented Oct 30, 2019

Okay, so to summarize I rolled back fold and foldM.

@neko-kai argued to leave them on grounds that fold can always be replaced by map when the error is Nothing, and foldM can always be replaced by flatMap when the error is Nothing.

I think it is a valid argument, it just concerned me when I saw, e.g. foldM being replaced by foldCauseM merely to work around the (new) compiler error.

What do you think?

Making similar changes to other ZIO data types.

Makes sense.

First, the name CanFail now makes it very clear what it is, thanks to your good suggestion to use a more specific type constraint, and we can turn it into a feature.

I think you're right about this, CanFail is good enough.

Testing - what do you think about pursuing a separate PR to add some "does not compile" functionality to ZIO Test.

Agreed, if we had something that was cross Scala (2.11 - Dotty), it could make sense to add to ZIO Test, and that's precisely the case when I'd feel comfortable adding it here.

@vasilmkd
Copy link
Contributor

vasilmkd commented Oct 30, 2019

Probably a good idea to watch this talk, I found it pretty fascinating when I first saw it. There might be some good hints when it comes to naming as well:

Plain Functional Programming by Martin Odersky.

@adamgfraser
Copy link
Contributor Author

@jdegoes I don’t think uio.fold or uio.foldM should compile since they can always be rewritten in terms of map and flatMap. It was my mistake in not rewriting one of the tests correctly. What we should do is put together documentation showing how to rewrite code for this change. I will put together a table that we can reference in the release notes or elsewhere.

I will add back the changes to fold and foldM and make similar changes to other data types.

I have also been working on something to test that code does not compile so I will post that shortly, though that will be in a separate PR.

Did you have any interest in doing something similar for the environment type?

@adamgfraser
Copy link
Contributor Author

adamgfraser commented Oct 30, 2019

ZIO

Code Rewrite
uio <> zio uio
uio.asError(e) uio
uio.bimap(f, g) uio.map(g)
uio.catchAll(f) uio
uio.catchSome(pf) uio
uio.either uio*
uio.eventually uio
uio.flatMapError(f) uio
uio.fold(f, g) uio.map(g)
uio.foldM(f, g) uio.flatMap(g)
uio.mapError(f) uio
uio.option uio*
uio.orDie uio
uio.orDieWith(f) uio
uio.orElse(zio) uio
uio.orElseEither(zio) uio*
uio.refineOrDie(pf) uio
uio.refineOrDieWith(pf)(f) uio
uio.refineToOrDie uio
uio.retry(s) uio
uio.retryOrElse(s, f) uio
uio.retryOrElseEither(s, f) uio*
uio.tapBoth(f, g) uio.tap(g)
uio.tapError(f) uio

ZManaged

Code Rewrite
umanaged <> zmanaged umanaged
umanaged.bimap(f, g) umanaged.map(g)
umanaged.catchAll(f) umanaged
umanaged.catchSome(pf) umanaged
umanaged.either umanaged*
umanaged.flatMapError(f) umanaged
umanaged.fold(f, g) umanaged.map(f)
umanaged.foldM(f, g) umanaged.flatMap(g)
umanaged.mapError(f) umanaged
umanaged.option umanaged*
umanaged.orDie umanaged
umanaged.orDieWith(f) umanaged
umanaged.orElse(zmanaged) umanaged
umanaged.orElseEither(zmanaged) umanaged
umanaged.refineOrDie(pf) umanaged
umanaged.refineToOrDie umanaged
umanaged.refineToOrDieWith(pf)(f) umanaged
umanaged.retry(s) umanaged

ZStream

Code Rewrite
ustream.bimap(f, g) ustream.map(g)
ustream.catchAll(f) ustream
ustream.either ustream*
ustream.mapError(f) ustream
ustream.orElse(zstream) ustream

ZStreamChunk

Code Rewrite
ustream.either ustream
ustream.orElse(zstream) ustream
  • either, option, orElseEither, and retryOrElseEither wrap their results in Some or Right so after rewriting, code calling these methods can be simplified to accept an A rather than an Option[A] or Either[E, A].

@adamgfraser
Copy link
Contributor Author

This is ready for another review.

@jdegoes
Copy link
Member

jdegoes commented Oct 31, 2019

I don’t think uio.fold or uio.foldM should compile since they can always be rewritten in terms of map and flatMap. It was my mistake in not rewriting one of the tests correctly. What we should do is put together documentation showing how to rewrite code for this change. I will put together a table that we can reference in the release notes or elsewhere.

OK, sounds great!

Did you have any interest in doing something similar for the environment type?

Yes, I think it's worthwhile to do that. Maybe RequiresEnv for that one?

@adamgfraser
Copy link
Contributor Author

Okay, great! I will open a separate PR to do the same thing for the environment type. Maybe NeedsEnv? Happy to do either.

@jdegoes
Copy link
Member

jdegoes commented Oct 31, 2019

Like NeedsEnv 👍

@jdegoes jdegoes merged commit 6e23640 into zio:master Oct 31, 2019
implicit final def canFail[E]: CanFail[E] = CanFail

implicit final val canFailAmbiguous1: CanFail[Nothing] = CanFail
implicit final val canFailAmbiguous2: CanFail[Nothing] = CanFail
Copy link
Member

Choose a reason for hiding this comment

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

For my own understanding, why is there two such values? Also it may be useful to add an explanatory comment for the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want implicit search to fail when E =:= Nothing so we create two identical instances of CanFail[Nothing] to ensure that the result will always be ambiguous. I can add some documentation when I do the same thing the environment type.

@adamgfraser
Copy link
Contributor Author

I'm on it!

@adamgfraser adamgfraser deleted the 2047 branch October 31, 2019 10:10
ghostdogpr pushed a commit to ghostdogpr/scalaz-zio that referenced this pull request Nov 5, 2019
* implement CanFail

* fix typo

* cache values

* update documentation

* fix type inference issue

* turn off warning for unused implicit parameters

* fix dotty compatibility issue

* address review comments

* add back fold and foldM

* add <>

* add ZManaged

* add ZStream

* add ZStreamChunk

* cleanup

* remove extraneous type parameter
Twizty pushed a commit to Twizty/zio that referenced this pull request Nov 13, 2019
* implement CanFail

* fix typo

* cache values

* update documentation

* fix type inference issue

* turn off warning for unused implicit parameters

* fix dotty compatibility issue

* address review comments

* add back fold and foldM

* add <>

* add ZManaged

* add ZStream

* add ZStreamChunk

* cleanup

* remove extraneous type parameter
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.

Compile-time errors for useless combinators
6 participants