Scala中类型lambda与高阶类型结合:如何让编译器自动推导类型?
这个问题的核心是Scala编译器无法自动将类型投影(比如single[String]#t)与高阶类型参数(比如A[X,Y]或D[A<:Item])匹配——因为类型投影会被处理成存在类型,而方法需要的是可追踪的具体类型构造器。下面给出几种不同场景的解决方案:
方案1:用辅助类封装类型构造器(Scala 2通用)
原来的single类型用结构类型生成类型投影,导致编译器无法识别具体的类型构造器。我们可以用一个普通类来封装这个类型成员,让编译器能追踪它的明确类型:
针对第一个简单示例修改:
trait Func[A[X, Y]] { def apply[X, Y](a: A[X, Y]): A[X, Y] } // 用普通类替代结构类型的single class Single[T] { type t[X, Y] = T } // 创建具体的Single实例,它的t成员是明确的类型构造器 val stringSingle = new Single[String] val aInstance: Func[stringSingle.t] = new Func[stringSingle.t] { def apply[X, Y](a: String): String = a.toUpperCase // 实现你的业务逻辑 } def test[A[X, Y]](f: Func[A]): Unit = println("调用成功!") // 现在编译器可以自动推导类型,无需手动指定 test(aInstance)
针对业务场景示例修改:
object GenericTest { trait Item { def name: String } class ItemA extends Item { def name: String = "a" } class ItemB extends Item { def name: String = "b" } trait MapFn[-A[X <: Item], +B[X <: Item]] { def apply[X <: Item](data: A[X]): B[X] } case class ItemsCollection[C[A <: Item]](a: C[ItemA], b: C[ItemB]) { def map[D[A <: Item]](f: MapFn[C, D]): ItemsCollection[D] = ItemsCollection(f(a), f(b)) } // 辅助类替代原来的结构类型single class Single[T] { type t[A <: Item] = T } val itemSeq = ItemsCollection[Seq](Seq(new ItemA), Seq(new ItemB)) val itemSet = itemSeq.map(new MapFn[Seq, Set] { override def apply[X <: Item](data: Seq[X]): Set[X] = data.toSet }) // 创建具体的Single实例,用它的t成员作为类型构造器 val stringSingle = new Single[String] val itemNames = itemSeq.map(new MapFn[Seq, stringSingle.t] { override def apply[X <: Item](data: Seq[X]): String = data.head.name }) }
方案2:使用Kind Projector插件(Scala 2推荐)
如果你可以添加编译器插件,Kind Projector允许你直接用简洁的语法定义类型lambda,不需要手动写辅助类。
首先在build.sbt中添加插件:
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full)
然后修改代码:
第一个示例:
trait Func[A[X, Y]] { def apply[X, Y](a: A[X, Y]): A[X, Y] } // 直接用[X,Y] => String表示类型lambda val aInstance: Func[[X,Y] => String] = new Func[[X,Y] => String] { def apply[X, Y](a: String): String = a.toUpperCase } def test[A[X, Y]](f: Func[A]): Unit = println("调用成功!") test(aInstance) // 自动推导成功
业务场景示例:
object GenericTest { // ... 保留原来的Item、MapFn、ItemsCollection定义 ... val itemSeq = ItemsCollection[Seq](Seq(new ItemA), Seq(new ItemB)) val itemSet = itemSeq.map(new MapFn[Seq, Set] { override def apply[X <: Item](data: Seq[X]): Set[X] = data.toSet }) // 直接用[A <: Item] => String表示类型lambda val itemNames = itemSeq.map(new MapFn[Seq, [A <: Item] => String] { override def apply[X <: Item](data: Seq[X]): String = data.head.name }) }
方案3:Scala 3内置类型lambda(推荐,若升级到Scala 3)
Scala 3原生支持类型lambda语法,不需要任何插件,写法更简洁直观:
第一个示例:
trait Func[A[X, Y]] { def apply[X, Y](a: A[X, Y]): A[X, Y] } // 原生类型lambda语法 val aInstance: Func[[X,Y] => String] = new Func[[X,Y] => String] { def apply[X, Y](a: String): String = a.toUpperCase } def test[A[X, Y]](f: Func[A]): Unit = println("调用成功!") test(aInstance) // 自动推导无压力
业务场景示例:
object GenericTest { // ... 保留原来的Item、MapFn、ItemsCollection定义 ... val itemSeq = ItemsCollection[Seq](Seq(new ItemA), Seq(new ItemB)) val itemSet = itemSeq.map(new MapFn[Seq, Set] { override def apply[X <: Item](data: Seq[X]): Set[X] = data.toSet }) // 原生类型lambda,直接指定输出为String val itemNames = itemSeq.map(new MapFn[Seq, [A <: Item] => String] { override def apply[X <: Item](data: Seq[X]): String = data.head.name }) }
为什么原来的写法会失败?
原来的single[String]#t是类型投影,编译器会将其视为一个"存在类型"(类似于_$1[X,Y],其中_$1是未知的类型构造器)。而方法test或map需要的是一个具体、可命名的类型构造器,编译器无法将模糊的存在类型与方法的高阶类型参数匹配,因此必须手动指定类型。上面的方案都是提供了具体的类型构造器,让编译器能追踪到明确的类型信息,从而自动推导。
内容的提问来源于stack exchange,提问作者Maxim
相关产品推荐
相关产品推荐

