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.
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
})
}