Search code examples
scalageneric-programmingscala-cats

Filter a F[List[Int]] using an Int => F[Boolean] where F is generic


I'm trying to define an abstract algebra that will allow me to defer choosing what Monad I will use to wrap an effectful operation (IO, Task, Future, etc) until I run the program.

trait MyAlg[F[_]]
  def isValid(v: int): F[Boolean]
  def getElements(): F[List[Int]]
  def filterValidElements(vs: F[List[Int]]): F[List[Int]]

Imagine that I need to do something possibly side-effecting in isValid, like check the db.

What would be nice is if I could leave isValid and getElements abstract --- so that for example one implementation could connect to a database and another could refer to a mock for testing --- but define a generic filterValidElements so I don't need to re-impliment the same function for both cases. Something like this:

def filterValidElements(es: F[List[Int]]]): F[List[Int]] = 
      es.map(
        elems => elems.map(
          e => (e, isValid(e))).collect{
            case (e, F(true)) => e
       })

However,F is generic, so it doesn't provide map and doesn't have a constructor.

Of course, I can't explicity set F to be a Monad, for example like

trait MyAlg[F: cats.Monad]

because traits cannot have type parameters with context bounds.

Is there any way to write my filterValidElements function, leaving isValid abstract and F generic?

I am pretty new with this style, so I may be going about this entirely the wrong way.


Solution

  • Try to add implicit parameters specifying that you can do map (i.e. Functor) and pure aka point (i.e. InvariantMonoidal). For example you can make it Applicative or Monad.

    import cats.{Functor, InvariantMonoidal}
    import cats.syntax.functor._
    import scala.language.higherKinds
    
    trait MyAlg[F[_]] {
      def isValid(v: Int): F[Boolean]
      def getElements(): F[List[Int]]
      def filterValidElements(es: F[List[Int]])(implicit functor: Functor[F], im: InvariantMonoidal[F]): F[List[Int]] =
        es.map(
          elems => elems.map(
            e => (e, isValid(e))).collect{
              case (e, fb) if fb == im.point(true) => e
            })
    }