Search code examples
scalafunctional-programmingscala-catstagless-final

Can't make simple tagless final example


I've read about tagless final and I think it's great. I wanted to build my own small example of this pattern and got the problem.

This is my code:

  trait Calculator[F[_]] {
    def sum(a: Int, b: Int): F[Either[Throwable, Int]]
    def minus(a: Int, b: Int): F[Either[Throwable, Int]]
  }

  object calculatorInstances {
    implicit val futureCalculator: Calculator[Future] = new Calculator[Future] {
      override def sum(a: Int, b: Int) =
        Future {
          Try(a + b).toEither
        }
      override def minus(a: Int, b: Int) =
        Future {
          Try(a - b).toEither
        }
    }
  }


  import calculatorInstances.futureCalculator
  def getPlusAndMinus[F[_]: Monad: Calculator](a: Int, b: Int): F[Either[String, Int]] = {
    for {
      sum <- Calculator[F].sum(a, b)
      res <- Calculator[F].minus(sum, b)
    } yield res
  }

This code doesn't work because of not found: value Calculator error. How can I do it properly?


Solution

  • Add materializer:

    object Calculator {
      def apply[F[_]: Calculator]: Calculator[F] = implicitly
    }
    

    It's better to put instances of type class Calculator[F[_]] (like implicit futureCalculator) to the same companion object Calculator, otherwise you'll have to import calculatorInstances._.

    Don't forget to import cats.syntax.flatMap._ and import cats.syntax.functor._.

    sum in sum <- Calculator[F].sum(a, b) is of type Either[Throwable,Int] but sum in Calculator[F].minus(sum, b) is expected to be Int.

    Maybe returning type of getPlusAndMinus should be F[Either[Throwable, Int]] instead of F[Either[String, Int]].

    Maybe the easiest way to fix for comprehension is to use monad transformer:

    def getPlusAndMinus[F[_] : Monad: Calculator](a: Int, b: Int): F[Either[Throwable, Int]] = {
      (for {
        sum <- EitherT(Calculator[F].sum(a, b))
        res <- EitherT(Calculator[F].minus(sum, b))
      } yield res).value
    }
    

    Just in case, the whole code:

    import cats.data.EitherT
    import cats.Monad
    //import cats.syntax.flatMap._ // not necessary if we use EitherT
    //import cats.syntax.functor._
    
    import scala.concurrent.Future
    import scala.language.higherKinds
    import scala.util.Try
    import scala.concurrent.ExecutionContext.Implicits.global
    
    object App {
    
      trait Calculator[F[_]] {
        def sum(a: Int, b: Int): F[Either[Throwable, Int]]
        def minus(a: Int, b: Int): F[Either[Throwable, Int]]
      }
    
      object Calculator {
        def apply[F[_]: Calculator]: Calculator[F] = implicitly
    
        implicit val futureCalculator: Calculator[Future] = new Calculator[Future] {
          override def sum(a: Int, b: Int) =
            Future {
              Try(a + b).toEither
            }
          override def minus(a: Int, b: Int) =
            Future {
              Try(a - b).toEither
            }
        }
      }
    
      def getPlusAndMinus[F[_] : Monad: Calculator](a: Int, b: Int): F[Either[Throwable, Int]] = {
        (for {
          sum <- EitherT(Calculator[F].sum(a, b))
          res <- EitherT(Calculator[F].minus(sum, b))
        } yield res).value
      }
    }