Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
    Nathaniel Fischer
    @kag0
    This idea needs some work to be consistent, but it would be nice if there were some helper method that let you move an "inner" error to an "outer" one more easily.
    For example (value: EitherT[Future, A :+: B :+: CNil, Result]).ignore(identity[B]) results in an EitherT[Future, A, Result] given B is a Throwable.
    This could maybe use a MonadError[Future, Throwable].
    Nathaniel Fischer
    @kag0
    That would be a lot more ergonomic than value.handleSomeWith((e: B) => EitherT(Future.failed(e))
    Jacob Wang
    @jatcwang
    that should be easy enough to do
    Jacob Wang
    @jatcwang
    Can't really use MonadError though because it completely hides the error type (It deals with F[A])
    Nathaniel Fischer
    @kag0
    Could we do liike
    type TheMonadErrorWeNeed[F[_, _], R, E] = MonadError[F[*, R], E]
    def ignore[L1, L1Out, E](funct: L1 => E)(implicit monadError: TheMonadErrorWeNeed[F, R, E], ...)
    in ErrorTransCoproductOps?
    Nathaniel Fischer
    @kag0
    Or I suppose we could just make our own thing like
    trait Raisable[F[_, _], E] {
      def raise[L, R](f: F[L, R])(e: => E): F[L, R]
    }
    object Raisable {
      implicit def eitherTRaisable[F[_], E](implicit monadError: MonadError[F, E]) = new Raisable[EitherT[F, _, _], E] {
        def raise[L, R](f: EitherT[F, L, R])(e: => E): EitherT[F, L, R] = f.flatMapF(_ => monadError.raiseError(e))
      }
    }
    Jacob Wang
    @jatcwang

    I'm defining something like

    trait ErrorTransThrow[F[_, _]] extends ErrorTrans[F] {
      def raiseError[L, E, R, LL](in: F[L, R]): F[LL, R]
    }

    I don't think user will want to create an implicit TheMonadErrorWeNeed for every error(s) they want to throw

    Nathaniel Fischer
    @kag0
    I don't really understand the ErrorTransThrow. Should raiseError have a second parameter list?
    Jacob Wang
    @jatcwang
    yes it would (need some sort of elimination function). Still working on it :)
    Nathaniel Fischer
    @kag0
    @jatcwang
    Nice, that syntax looks very concise. But what if the LHS error isn't a throwable? It might be more generally useful to let ErrorTransThrow work for any E and/or have dieIf take a parameter L1 => E.
    also, would it work to use scala.DummyImplicit instead of DummyParam?
    Jacob Wang
    @jatcwang

    @kag0 nice Didn't know about DummyImplicit :D

    I'm wondering what are you going to do with the errors you specified if it's not a throwable? (I don't really want to encourage people do silently ignore errors..)

    If you want to transform particular errors into the other type, you can use handleError to handle a subset of the errors and turn them into some other error type? (The result type should be correctly inferred)

    Note that I'm thinking of changing the names from handle1, handleSome to mapError1 and mapSomeError

    (following ZIO naming convention which I think makes a lot of sense)
    Nathaniel Fischer
    @kag0

    so that would look like

    thing
      .mapSomeError((e: MyError) => new MyOtherError(e))
      .dieIf[MyOtherError]

    ?

    That makes sense. Users can always make their own syntax if they want to shorten that up.
    As far as non-throwable errors, I'm just thinking of edge cases where maybe the error type is a subtype of Throwable, or maybe the user is possesed to have an Either[FatalE, Either[ExpectedE, Result]] or something.
    Jacob Wang
    @jatcwang
    yeah that'll work. We can provide a mapSomeAndDie but it's probably to keep the API surface small for now.
    Not too sure what you mean in the latter paragraph. I'm guessing you mean that the user wants to go from F[FatalE :+: Expected E :+: CNil, Result] to F[FatalE :+: CNil, F[ExpectedE, Result]? You can use flatmapError for that (it's currently named handleSomeWith)
    Jacob Wang
    @jatcwang
    Pushed my changes and tagged it 0.0.5
    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 Left(e) => EitherT(gMonadError.raiseError(e))
            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](
        implicit FE0: MonadError[F, E]
      ): MonadError[EitherT[F, L, ?], E]

    So let's say your monad is EitherT[cats.effect.IO, MyError, Unit], the MonadError instance you have is MonadError[IO, Throwable]

    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
    Will do :) Your feedback has been of great help already!
    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 :/
    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.