## Where communities thrive

• Join over 1.5M+ people
• Join over 100K+ communities
• Free without limits
##### Activity
Nathaniel Fischer
@kag0

A concrete example might be like

val before: Either[FatalE, Either[ExpectedE :+: FatalE :+: CNil, Result]] = ???
val after: Either[FatalE, Either[ExpectedE :+: CNil, Result]] = ???

so you're suggesting that a user could do

val after = before.flatMap(_.flatmapError((e: ExpectedE) => Right(Left(e))))

?

Nathaniel Fischer
@kag0

I was thinking the same could be done like

val after = EitherT(before).dieIf[FatalE]

you'd just need to remove the type constraint on E in ErrorTransThrow.extractAndThrow

Jacob Wang
@jatcwang
I guess I've never seen a signature like that, as you'd normally use flatMap and avoid it altogether (especially with the for comprehension support my library provides)
Jacob Wang
@jatcwang
(So to generalize, you want F[E1, F[E1 :+: E2 :+: CNil]] => F[E1 :+: E2 :+: CNil, F[E2 :+: CNil, R]]?
this is quite different from "dying", where you actually "blackholes" certain errors because they are thrown, terminating the whole chain. I cannot remove the Throwable constraint since ZIO instance requires it.
Nathaniel Fischer
@kag0

(So to generalize, you want F[E1, F[E1 :+: E2 :+: CNil]] => F[E1 :+: E2 :+: CNil, F[E2 :+: CNil, R]]?

errm, more like F[E1, F[E1 :+: E2 :+: CNil]] => F[E1, F[E2 :+: CNil, R]]. the example might be getting a bit convoluted/confusing.

really I'm just suggesting the typeclass look like
trait ErrorTransThrow[F[_, _], E] extends ErrorTrans[F] {
def extractAndThrow[L, R, LL](in: F[L, R])(extractUnhandled: L => Either[E, LL]): F[LL, R]
}
then ZIO can be supported with
implicit def zioErrorTrans[Env] =
new ErrorTransThrow[ZIO[Env, *, *], Throwable] {

override def extractAndThrow[L, R, LL](in: ZIO[Env, L, R])(
extractUnhandled: L => Either[Throwable, LL],
): ZIO[Env, LL, R] = in.catchAll { errors =>
extractUnhandled(errors) match {
case Left(throwable) => ZIO.die(throwable)
case Right(handledErrors) => ZIO.fail(handledErrors)
}
}
}
and eithert can be
implicit def eitherTErrorTrans[G[_], E](implicit gMonadError: MonadError[G, E]): ErrorTransThrow[EitherT[G, *, *], E] =
new ErrorTransThrow[EitherT[G, *, *], E] {

override def extractAndThrow[L, R, LL](in: EitherT[G, L, R])(
extractUnhandled: L => Either[E, LL]
) = in.leftFlatMap { l =>
extractUnhandled(l) match {
case Right(ll) => pureError(ll)
}
}
}
Nathaniel Fischer
@kag0
then my convoluted example should work fine
Jacob Wang
@jatcwang

I think there's a generalization hidden in those examples somewhere. It feels like some sort of leftFlatten (Although you're not flattening anything)

I get what you're trying to do, but how did you end up with Either[.., Either[..]] in the first place?
I'll need to spend some time thinking about the laws for these typeclasses, for some reason I don't think the EitherT and ZIO instance in your example is behaving the same

This is the definition of MonadError for EitherT

implicit def catsDataMonadErrorFForEitherT[F[_], E, L](
): MonadError[EitherT[F, L, ?], E]

not MyError
If you can show me some code where you need this 'plucking out error from nested F' behaviour, I can better grasp whether this is a problem that can be solved, or is already handled with the existing functionality
Nathaniel Fischer
@kag0

for some reason I don't think the EitherT and ZIO instance in your example is behaving the same

hmm, I guess a question is "could you write a lawful MonadError[ZIO, Throwable]?", and if so they should behave the same.

Jacob Wang
@jatcwang
I may have mislead myself looking at your example. At the end of the day you're looking to make some errors "poof" and go away. That was what I had before I added the Throwable constraint because it felt...unsafe. But I don't like the Throwable constraint either - If I can come up with some laws around the typeclass then I'm happy to change it. For now I'm trying to polish the API and work on docs / my presentation :)
Nathaniel Fischer
@kag0
let me know if I can be of assistance
Jacob Wang
@jatcwang
Stacy Curl
@stacycurl
Just heard your talk, very interesting. Have you considered using Iota instead of Shapeless ?
Jacob Wang
@jatcwang
I did very little research into Iota but I recall not being to do it (was missing some typeclasses that I need), but I should look at it again
Runtime performance is not an issue (you can run the benchmarks yourself). In the worst case with 100% failures of coproduct error with 8 error cases (and the error is the 8th error), It's about 1/3 of the speed of sealed traits. It's completely negligible in any real app.
Now, I'd love to see the compile time differences (if I can get all the features I want) because that's my current biggest worry
Jacob Wang
@jatcwang
On second thought, using macros instead of implicit search will kill type inference in intellij :/
Jacob Wang
@jatcwang
Nathaniel Fischer
@kag0
@jatcwang what do you think about scala 3 and hotpotato?
Jacob Wang
@jatcwang
Union types will solve the simple case, but won't solve the problem generally unfortunately.
I was initially quite excited about union types, but the more I think and use them, the more I think they may be an uncanny valley
For example, Option[String] | Option[Int] won't work due to type erasure
Nathaniel Fischer
@kag0
That's even worse than I thought
Do you think a scala 3 version of hotpotato would continue to use shapless then?
Jacob Wang
@jatcwang
If C#/F# gets them it'll work better because the have reified generics.
Second problem with union types is that they tend to fall apart when you're trying to abstract over them.
Nathaniel Fischer
@kag0
How do you mean?
Jacob Wang
@jatcwang
Compile time is a real concern right now - I think it'll improve in Scala 3 (What used to rely on implicit search now can use match types etc) but that's just my assumption.
I think it's worth looking into a Dotty reimplementation of https://github.com/frees-io/iota, which will rely on implicit search a lot less
Jacob Wang
@jatcwang
• You can't eliminate a type from a union without potentially eliminating other types that are subtypes of the type you want to eliminate
Ultimately all issue stems from union type "squashing" all possible cases into one single value and rely on (unreliable) isInstanceOf check to recover the individual cases.
Jacob Wang
@jatcwang

Another issue is that there's no first class way to say

"Given that I have Show instances for A, B, C individually, I can't get a Show[A | B | C] unless you make Show covariant

But for this particular issue I think it's fixable (perhaps with some macros). I feel like type erasure is just going to cause endless pain
Nathaniel Fischer
@kag0
It already has. Maybe some day someone will fork the jvm without type erasure and break backwards compat, but I doubt it.
Jacob Wang
@jatcwang
In some ways type erasure is good because that's what allowed so many different languages to exist on the JVM. With a language that supports typeclasses well it hasn't caused me too many issues. However because union types seems like such an obvious solution to error handling, my fear is that people will not find out about these issues (abstraction difficulty, inability to handle generic types) until it's too late
Nathaniel Fischer
@kag0
I've been bitten by case x: A (where A is a type parameter) before, even with class tags.
Hopefully there isn't a "too late" and things do work eventually. I'm less than enthused about the state of error handling at the moment.
Jacob Wang
@jatcwang
Dotty comes with improvements to type inference and path dependent types, so if shapeless (or its successor) is ported to Scala I'm sure hotpotato will be even better