Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@kitlangton
Copy link
Member

ZIO uses many implicit evidence constraints to implement methods on the ZIO trait that only work with particular subtypes. For instance, ZIO#some, which requires that the A type is optional, is defined as:

final def some[B](implicit ev: A <:< Option[B]): ZIO[R, Option[E], B] = ???

This is a very cool technique. The only problem is that the error message requires the user to understand how this technique works, particularly the meaning of the <:< symbol:

[error] /Users/kit/code/example.scala:17:25: Cannot prove that Int <:< Option[B].
[error]   val example = UIO(10).some

This could be clearer. Basically, by making our own versions, we can give more ZIO specific compile errors:

image

We could make this more specific for particularly difficult to use messages, potentially linking to documentation. Overall, this is basically an extension of the pattern used by the CanFail constraint.

@kitlangton kitlangton requested a review from iravid as a code owner March 21, 2021 03:54
@kitlangton kitlangton force-pushed the helpful-implicit-errors branch from a8977a3 to 5ccad87 Compare March 21, 2021 04:53

import scala.annotation.implicitNotFound

@implicitNotFound("\nOutput Type Mismatch\n expected: ${B}\n actual: ${A}\n\n")
Copy link
Contributor

Choose a reason for hiding this comment

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

What about something like:

This operator requires that the output type be a subtype of Option[Int] but the actual type String was not a subtype of Option[Int]

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree πŸ‘ Wasn't sure about the alignment / terseness. How about just the follow, so as not to repeat the expected type?
This operator requires that the output type be a subtype of Option[Int] but the actual type was String.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes.

override def apply(a: A): B = subtype(a)
}

implicit def implNothing[B]: HasOutput[Nothing, B] = new HasOutput[Nothing, B] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need these second instances?

Copy link
Member Author

@kitlangton kitlangton Mar 21, 2021

Choose a reason for hiding this comment

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

I know, right? Some more Scala weirdness. I needed them for some reason. It couldn't find one for Nothing, which means it didn't trigger the CanFail warning, but instead said gave the implicit error for HasError.

import scala.annotation.implicitNotFound

@implicitNotFound("\nOutput Type Mismatch\n expected: ${B}\n actual: ${A}\n\n")
sealed abstract class HasOutput[-A, +B] extends (A => B)
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we want to avoid using the term Has here given its existing definition as another data type? Something like IsSubtypeOutput or IsSubtypeOfOutput might be more descriptive and reads relatively well in infix position. Like implicit ev: A IsSubtypeOfOutput Option[B]. We could simplify if we didn't care about distinguishing the channels but I think that is helpful to indicate to the user where something went wrong.

Copy link
Member Author

Choose a reason for hiding this comment

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

That crossed my mindβ€”Has is accounted for. I like implicit ev: A IsSubtypeOfOutput Option[B] a lot.

@kitlangton
Copy link
Member Author

Updated @adamgfraser, but I left STM and ZStream alone for now, as I'm not sure how completely those files are going to change. Though I suppose most of ZIO will as well :P

@adamgfraser
Copy link
Contributor

@kitlangton Looks good! Yes I think we're going to have changes to everything to some extent so can't let that stop us. I think this is probably a good point to get feedback from others though and if we have alignment it will be easy to push to the streams and STM as well.

@kitlangton
Copy link
Member Author

Sounds good! I'll poke some people on Monday for feedback :)

@kitlangton kitlangton force-pushed the helpful-implicit-errors branch from dc43f98 to 6e8bdaf Compare March 21, 2021 05:30
iravid
iravid previously approved these changes Mar 21, 2021
Copy link
Member

@iravid iravid left a comment

Choose a reason for hiding this comment

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

Big like πŸ‘

@joroKr21
Copy link
Contributor

Have you considered using a type alias? @implicitNotFound should work on type aliases

@adamgfraser
Copy link
Contributor

@joroKr21 That's interesting! I didn't realize you could do that! Unfortunately it doesn't seem to work on Scala 3.

import scala.annotation.implicitNotFound

@implicitNotFound("${A} is not a subtype of ${B}")
type IsSubtypeOf[-A, +B] = A <:< B

object Example {
  implicitly[String IsSubtypeOf Int]
}

// Cannot prove that String <:< Int.

Maybe the Scala 3 team can resolve that. It would certainly be nice to make the implementation of this lighter weight. I will open an issue.

@adamgfraser
Copy link
Contributor

Ah, I think this may be resolved by scala/scala3#11823. I commented to confirm.

@kitlangton
Copy link
Member Author

Have you considered using a type alias? @implicitNotFound should work on type aliases

Oh cool. Also had no idea that worked :P
But yeah, it looks like they're leaning towards not supporting this in Scala 3. Maybe we could use zio-prelude's Subtype trick? However, as currently there are only two new types, I don't think its too terrifying a sum of boilerplate πŸ˜‰.

@adamgfraser
Copy link
Contributor

I totally agree. Hopefully they will address in Scala 3 but otherwise this is already pretty minimal and any new type machinery is going to be more heavy weight than what you have here.

@kitlangton
Copy link
Member Author

kitlangton commented Mar 21, 2021

Him, though it looks like there may be some other issues on dotty right now:

[info] compiling 106 Scala sources and 1 Java source to /home/circleci/project/core/jvm/target/scala-3.0.0-RC1/classes ...
[error] -- [E008] Not Found Error: /home/circleci/project/core/shared/src/main/scala/zio/ZIO.scala:934:18 
[error] 934 |    map(result => !result)
[error]     |                  ^^^^^^^
[error]     |                  value unary_! is not a member of A
[error] -- [E008] Not Found Error: /home/circleci/project/core/shared/src/main/scala/zio/ZIO.scala:942:13 
[error] 942 |      a => a.fold[ZIO[R, Option[E], Unit]](ZIO.succeedNow(()))(_ => ZIO.fail(None))
[error]     |           ^^^^^^
[error]     |           value fold is not a member of A

Not sure if that's because of my changes. I'll have to take a look.

It looks like maybe the implicit conversion isn't firing as it does with (implicit ev: A <:< Option[B]). Hrm.

@joroKr21
Copy link
Contributor

joroKr21 commented Mar 21, 2021

But yeah, it looks like they're leaning towards not supporting this in Scala 3.

That's a shame. Having a different class means you can't convert back and forth. You are converting from <:< to your custom type but not the other way around so if I have your type I can't use a function that expects <:<

@kitlangton
Copy link
Member Author

kitlangton commented Mar 21, 2021

That's a shame. Having a different class means you can't convert back and forth. You are converting from <:< to your custom type but not the other way around so if I have your type I can't use a function that expects <:<

Oh, good point! What if I make these type extend <:<?

@joroKr21
Copy link
Contributor

Oh, good point! What if I make these type extend <:<?

You could try, but that sounds like it would cause a circular dependency

@kitlangton kitlangton force-pushed the helpful-implicit-errors branch from 9c3229e to 4542c68 Compare April 28, 2021 07:05
@kitlangton
Copy link
Member Author

But yeah, it looks like they're leaning towards not supporting this in Scala 3.

That's a shame. Having a different class means you can't convert back and forth. You are converting from <:< to your custom type but not the other way around so if I have your type I can't use a function that expects <:<

I actually don't think this will be a problem, as this will only be used internally, and they can be derived from <:<, so as a user, you should never run into this.

Copy link
Contributor

@adamgfraser adamgfraser left a comment

Choose a reason for hiding this comment

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

Let's get this merged!

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.

4 participants