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

Should define error handing in DSL ? #5

Open
pandaforme opened this issue Feb 14, 2017 · 1 comment
Open

Should define error handing in DSL ? #5

pandaforme opened this issue Feb 14, 2017 · 1 comment

Comments

@pandaforme
Copy link

Thank you for writing an excellent article about Free Monad

I try to apply this pattern to my current projects and face a problem.

  • Business flow:
    A user input a correct captcha then system will send a mail.

  • DSL:

    type Error = ???
    sealed trait CaptchaOps[A]
    case class Validate(captcha: String) extends CaptchaOps[Either[Error, Unit]]

    sealed trait NotifyOps[A]
    case class Send() extends NotifyOps[Unit]
  • Interpreters:
      val captchaOpsInterpreter = new (CaptchaOps ~> Future) {
        override def apply[A](fa: CaptchaOps[A]) = ???
      }

      val notifyOpsInterpreter = new (NotifyOps ~> Lambda[A => Future[Either[Error, A]]]) {
        override def apply[A](fa: NotifyOps[A]) = ???
      }
  • Programs:
      import cats.free._

      class CaptchaOpsI[F[_]](implicit I: Inject[CaptchaOps, F]) {
        def validateI(captcha: String): Free[F, Either[Errors, Unit]] =
          Free.inject[CaptchaOps, F](Validate(captcha))
      }

      class NotifyOpsI[F[_]](implicit I: Inject[NotifyOps, F]) {
        def sendI(): Free[F, Unit] = Free.inject[NotifyOps, F](Send(account))
      }

      implicit def captchaOpsI[F[_]](implicit I: Inject[CaptchaOps, F]): CaptchaOpsI[F] = new CaptchaOpsI[F]
      implicit def notifyOpsI[F[_]](implicit I: Inject[NotifyOps, F]): NotifyOpsI[F] = new NotifyOpsI[F]

      type CN[A] = Coproduct[CaptchaOps, NotifyOps, A]

      def program(implicit c: CaptchaOpsI[CN], n: NotifyOpsI[CN]) = {
        import c._
        import n._
        for {
          b <- validateI("right captcha")
          _ <- sendI()
        } yield {
          "Success !!!"
        }
      }

      val interpreter = captchaOpsInterpreter or notifyOpsInterpreter

      val result = program.foldMap(interpreter)

I can't compose captchaOpsInterpreter and notifyOpsInterpreter altogether because their context is not the same.

The context of captchaOpsInterpreter is Future
The context of notifyOpsInterpreter is Future[Either[Error, ?]]]

I try to unify the context and use EitherT

      val captchaOpsInterpreter = new (CaptchaOps ~> EitherT[Future, Error, ?]) {
        override def apply[A](fa: CaptchaOps[A]) = fa match {
          case Validate(captcha: IdentityCaptcha) =>
            EitherT.right[Future, Error, A](Future(().asInstanceOf[A]))
        }
      }

      val notifyOpsInterpreter = new (NotifyOps ~> EitherT[Future, Errors, ?]) {
        override def apply[A](fa: NotifyOps[A]) = fa match {
          case Send(account: BusinessEmailAccount) =>
            EitherT.right[Future, Error, A](Future(().asInstanceOf[A]))
        }
      }

Passed compiling check but I got a runtime error

java.lang.ClassCastException: scala.runtime.BoxedUnit cannot be cast to scala.util.Either

I find out that Captcha DSL I defined makes me hard to unify their context to Future[Either[Error, ?]]]

I modify Captcha DSL and can unify their context easily:

    sealed trait CaptchaOps[A]
    case class Validate(captcha: String) extends CaptchaOps[Unit]

Question:

  • The last version DSL of CaptchaOps doesn't express the intention: If validated successfully return Unit; If validated failed return Error
  • The first version DSL of CaptchaOps makes me hard to unify their context to Future[Either[Error, ?]]].

If I define error handling in DSL, then I'll face:

  1. hard to unify their context
  2. need Monad transformer to get result

If I don't define error handling in DSL, I'm afraid that DSL doesn't express its intention.

I want to know your suggestion :>

@pvillega
Copy link
Owner

Hi, first of all apologies for the delay, I just noticed this!

First of all, I'm wondering if you can't compose the initial interpreters because you are using different types on the natural transformation: CaptchaOps ~> FuturevsNotifyOps ~> Lambda[A => Future[Either[Error, A]]]. This could be the reason of your error, try build both to Futureand theextends CaptchaOps[Either[Error, Unit]]in the case class will provide theEitheras theA` received in the natural transformation. Should work.

Second, as an alternative to compose languages, look at https://github.com/ProjectSeptemberInc/freek . It will simplify the boilerplate and the examples may help you build the stack you want

Cheers

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

No branches or pull requests

2 participants