我正在尝试定义一个抽象代数,使我可以推迟选择要使用的Monad来包装有效的操作(IO,Task,Future等),直到运行程序为止。
trait MyAlg[F[_]]
def isValid(v: int): F[Boolean]
def getElements(): F[List[Int]]
def filterValidElements(vs: F[List[Int]]): F[List[Int]]
想象一下,我需要在isValid
中做一些可能会产生副作用的事情,例如检查数据库。
如果我可以将isValid
和getElements
保留为抽象,那将是一个不错的选择,例如,一个实现可以连接到数据库,而另一个实现可以引用模拟进行测试---但要定义通用的filterValidElements
,因此在两种情况下都无需重新暗示相同的功能。像这样:
def filterValidElements(es: F[List[Int]]]): F[List[Int]] =
es.map(
elems => elems.map(
e => (e, isValid(e))).collect{
case (e, F(true)) => e
})
但是,F
是通用的,因此它不提供map
并且没有构造函数。
当然,我不能明确地将F
设置为Monad,例如
trait MyAlg[F: cats.Monad]
因为特征不能具有带有上下文边界的类型参数。
有什么方法可以编写我的filterValidElements
函数,而使isValid
是抽象的而F
是泛型的?
我对这种风格还很陌生,所以我可能会完全以错误的方式来做。
答案 0 :(得分:2)
尝试添加隐式参数,以指定您可以执行map
(即Functor
)和pure
aka point
(即InvariantMonoidal
)。例如,您可以将其设置为Applicative
或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
})
}