我正在尝试定义一个抽象代数,这将允许我推迟选择我将用于包装有效操作(IO,任务,未来等)的Monad直到我运行程序。
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]
因为traits不能具有带上下文边界的类型参数。
有没有办法写我的filterValidElements
函数,留下isValid
摘要和F
通用?
我对这种风格很陌生,所以我可能会以完全错误的方式解决这个问题。
尝试添加隐式参数,指定您可以执行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
})
}