Scala:如何将函子用于多类型参数特征
Scala: How to use Functors for multi type parameter trait
我有 ADT,它基本上是 Function1
:
的包装
case class Abstract[M[_], A, B](f:M[A] => M[B]) {
def fn: M[A] => M[B] = { case x: M[A] => f(x) }
}
我想映射这些,所以我定义了一个 Functor:
trait AbstractAPI[E] {
type AbsO[T] = Abstract[List, E, T]
// type AbsO[T] = Abstract[List, _, T] => does not work (?)
implicit val abstractO: Functor[AbsO] = new Functor[AbsO] {
def map[A, B](fa: AbsO[A])(f: A => B): AbsO[B] = {
new Abstract(fa.fn andThen { x: List[A] => x.map{ y => f(y) } })
}
}
}
现在,要实际映射到摘要,我需要 AbstractAPI[Int]
,例如
case object IntAbstractAPI extends AbstractAPI[Int]
object A {
import IntAbstractAPI._
val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }
val hey = (new Abstract(f)).map{ x => x.toInt }
}
或
object A extends AbstractAPI[Int] {
val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }
// FINALLY!
val res = (new Abstract(f)).map{ x => x.toInt }.map{ _.toFloat + 10f }
// Abstract[List, Int, Float] = Abstract(<function1>)
}
但是,在这种模式下,我必须为每个可能的 E
定义案例对象。这是我的问题:
- 这是使用函子的正确方法吗?
- 我如何为每个可能的案例对象自动创建
E
(或让编译器推断它?)
编辑 1:
进一步说明:上面的实现有效,但这个没有:
object A extends AbstractAPI {
val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }
val res = (new Abstract(f)).map{ x => x.toInt }.map{ _.toFloat + 10f }
// Abstract[List, Int, Float] = Abstract(<function1>)
}
给出编译错误:
value map is not a member of Abstract[List,Int,String]
我认为这是因为编译器无法为 Abstract[List,Int,String]
?
派生仿函数
回答你的第二个问题,你可以试试这个隐式 AbstractAPI[T]
工厂:
implicit def abstractAPI[T]: AbstractAPI[T] = new AbstractAPI[T] {}
AbstractAPI[T]
所需的任何隐含证据都应该有效,例如:
def f[T : AbstractAPI]: Unit = ()
f
您可以推导您不关心的类型参数的仿函数。
import cats.Functor
import cats.syntax.functor._
我会将 Abstract
上的第二个类型参数重命名为 X
,这会有所帮助
case class Abstract[M[_], X, A](f: M[X] => M[A]) // forget the fn bit for now
您不仅可以使用 val
创建类型类实例,还可以使用 def
创建类型类实例。允许有类型参数,也可以带其他隐式(但只能是隐式)参数。
type Abs1[X] = ({ type L[A] = Abstract[List, X, A] })
/*implicit*/ def abstract1[X]: Functor[Abs1[X]#L] = new Functor[Abs1[X]#L] {
override def map[A, B](fa: Abstract[List, X, A])(f: A => B): Abstract[List, X, B] =
Abstract(mx => fa.f(mx).map(f))
}
如果 map
是您对 List
的全部需求,您可以进一步概括任何具有 Functor
实例的 M[_]
。还将它放入 Abstract
的伴生对象中,无需额外的导入/继承等就可以找到它。
object Abstract {
// Abstract.MX[M, X]#L can be replaced with Abstract[M, X, ?] if you use kind-projector
type MX[M[_], X] = ({ type L[A] = Abstract[M, X, A] })
implicit def genericFunctor[M[_]: Functor, X] = new Functor[MX[M, X]#L] {
override def map[A, B](fa: Abstract[M, X, A])(f: A => B): Abstract[M, X, B] =
Abstract(mx => fa.f(mx).map(f)) // the implementation is the same
}
}
如果您为 M[_]
导入实例,它就可以工作
assert {
import cats.instances.list._ // get Functor[List]
// map is automatically picked up from Functor[Abstract[List, Int, ?]]
Abstract(identity[List[Int]])
.map(Vector.range(0, _))
.map(_.mkString(""))
.f(List(1, 2, 3)) == List("0", "01", "012")
}
assert {
import cats.instances.option._
Abstract(identity[Option[Int]])
.map(_ min 42)
.map(i => Range(i, i + 3))
.f(Some(11)) == Some(Range(11, 14))
}
你可以试试密码there
我有 ADT,它基本上是 Function1
:
case class Abstract[M[_], A, B](f:M[A] => M[B]) {
def fn: M[A] => M[B] = { case x: M[A] => f(x) }
}
我想映射这些,所以我定义了一个 Functor:
trait AbstractAPI[E] {
type AbsO[T] = Abstract[List, E, T]
// type AbsO[T] = Abstract[List, _, T] => does not work (?)
implicit val abstractO: Functor[AbsO] = new Functor[AbsO] {
def map[A, B](fa: AbsO[A])(f: A => B): AbsO[B] = {
new Abstract(fa.fn andThen { x: List[A] => x.map{ y => f(y) } })
}
}
}
现在,要实际映射到摘要,我需要 AbstractAPI[Int]
,例如
case object IntAbstractAPI extends AbstractAPI[Int]
object A {
import IntAbstractAPI._
val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }
val hey = (new Abstract(f)).map{ x => x.toInt }
}
或
object A extends AbstractAPI[Int] {
val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }
// FINALLY!
val res = (new Abstract(f)).map{ x => x.toInt }.map{ _.toFloat + 10f }
// Abstract[List, Int, Float] = Abstract(<function1>)
}
但是,在这种模式下,我必须为每个可能的 E
定义案例对象。这是我的问题:
- 这是使用函子的正确方法吗?
- 我如何为每个可能的案例对象自动创建
E
(或让编译器推断它?)
编辑 1: 进一步说明:上面的实现有效,但这个没有:
object A extends AbstractAPI {
val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }
val res = (new Abstract(f)).map{ x => x.toInt }.map{ _.toFloat + 10f }
// Abstract[List, Int, Float] = Abstract(<function1>)
}
给出编译错误:
value map is not a member of Abstract[List,Int,String]
我认为这是因为编译器无法为 Abstract[List,Int,String]
?
回答你的第二个问题,你可以试试这个隐式 AbstractAPI[T]
工厂:
implicit def abstractAPI[T]: AbstractAPI[T] = new AbstractAPI[T] {}
AbstractAPI[T]
所需的任何隐含证据都应该有效,例如:
def f[T : AbstractAPI]: Unit = ()
f
您可以推导您不关心的类型参数的仿函数。
import cats.Functor
import cats.syntax.functor._
我会将 Abstract
上的第二个类型参数重命名为 X
,这会有所帮助
case class Abstract[M[_], X, A](f: M[X] => M[A]) // forget the fn bit for now
您不仅可以使用 val
创建类型类实例,还可以使用 def
创建类型类实例。允许有类型参数,也可以带其他隐式(但只能是隐式)参数。
type Abs1[X] = ({ type L[A] = Abstract[List, X, A] })
/*implicit*/ def abstract1[X]: Functor[Abs1[X]#L] = new Functor[Abs1[X]#L] {
override def map[A, B](fa: Abstract[List, X, A])(f: A => B): Abstract[List, X, B] =
Abstract(mx => fa.f(mx).map(f))
}
如果 map
是您对 List
的全部需求,您可以进一步概括任何具有 Functor
实例的 M[_]
。还将它放入 Abstract
的伴生对象中,无需额外的导入/继承等就可以找到它。
object Abstract {
// Abstract.MX[M, X]#L can be replaced with Abstract[M, X, ?] if you use kind-projector
type MX[M[_], X] = ({ type L[A] = Abstract[M, X, A] })
implicit def genericFunctor[M[_]: Functor, X] = new Functor[MX[M, X]#L] {
override def map[A, B](fa: Abstract[M, X, A])(f: A => B): Abstract[M, X, B] =
Abstract(mx => fa.f(mx).map(f)) // the implementation is the same
}
}
如果您为 M[_]
assert {
import cats.instances.list._ // get Functor[List]
// map is automatically picked up from Functor[Abstract[List, Int, ?]]
Abstract(identity[List[Int]])
.map(Vector.range(0, _))
.map(_.mkString(""))
.f(List(1, 2, 3)) == List("0", "01", "012")
}
assert {
import cats.instances.option._
Abstract(identity[Option[Int]])
.map(_ min 42)
.map(i => Range(i, i + 3))
.f(Some(11)) == Some(Range(11, 14))
}
你可以试试密码there