Search code examples
scalafunctional-programmingmonadsscalazscala-cats

Avoiding boilerplate with OptionT (natural transform?)


I have the following methods:

trait Tr[F[_]]{

    def getSet(): F[Set[String]]

    def checksum(): F[Long]

    def value(): F[String]

    def doRun(v: String, c: Long, s: Set[String]): F[Unit]
}

now I want to write the following for comprehension:

import cats._
import cats.data.OptionT
import cats.implicits._

def fcmprhn[F[_]: Monad](F: Tr[F]): OptionT[F, Unit] =
  for {
    set <- OptionT {
      F.getSet() map { s =>
        if(s.nonEmpty) Some(s) else None
      }
    }
    checksum <- OptionT.liftF(F.checksum())
    v <- OptionT.liftF(F.value())
    _ <- OptionT.liftF(F.doRun(v, checksum, set))
    //can be lots of OptionT.liftF here
} yield ()

As you can see there is too much of OptionT boilerplate. Is there a way to avoid it?

I think I can make use of F ~> OptionT[F, ?]. Can you suggest something?


Solution

  • One approach could be to nest the "F only" portion of the for-comprehension within a single liftF:

    def fcmprhn[F[_]: Monad](F: Tr[F]): OptionT[F, Unit] =
      for {
        set <- OptionT {
          F.getSet() map { s =>
            if(s.nonEmpty) Some(s) else None
          }
        }
        _ <- OptionT.liftF {
          for {
            checksum <- F.checksum()
            v <- F.value()
            _ <- F.doRun(v, checksum, set)
            // rest of F monad for-comprehension
          } yield ()
        }
      } yield ()