Search code examples
scalazkleisli

How to reduce boilerplate with Kleisli


I follow the design of the book Functional and Reactive Domain Modeling

And for some service methods, it only delegates work to the repository layer. Is there a way to reduce this boilerplate :

trait FeedbackServiceImpl extends FeedbackService {
  override def saveTFE(feedback: TripFeedbackEvent) =
    Kleisli[Future, Context, Either[String, Id]] { ctx => ctx.feedbackRepo.save(feedback) }

  override def saveLFE(feedback: LibraryFeedbackEvent) =
    Kleisli[Future, Context, Either[String, Id]] { ctx => ctx.feedbackRepo.save(feedback) }

  override def findByUser(userId: Id) =
    Kleisli[Future, Context, Seq[FeedbackEvent]] { ctx => ctx.feedbackRepo.findByUser(userId) }

  override def all =
    Kleisli[Future, Context, Seq[FeedbackEvent]] { ctx => ctx.feedbackRepo.all }

  override def findByTip(tipId: Id) =
    Kleisli[Future, Context, Seq[FeedbackEvent]] { ctx => ctx.feedbackRepo.findByTip(tipId) }

}

Solution

  • We can create a combinator :

    private def kleisli[M[_], A](f: FeedbackRepository => M[A]) = Kleisli.kleisli(f).local[Context](_.feedbackRepo)

    Hence we gain 2 things :

    • avoid declaring the type by helping the type inference mechanism
    • avoid calling ctx.feedbackRepo by using local

    So we can use :

    trait Feedbacks {
      def saveTFE(feedback: TripFeedbackEvent) = kleisli(_.save(feedback)) 
      def saveLFE(feedback: LibraryFeedbackEvent) = kleisli(_.save(feedback))
      def findByUser(userId: Id) = kleisli(_.findByUser(userId))
      ...
    }