克服高阶函数参数的Scala型擦除

克服高阶函数参数的Scala型擦除,scala,functional-programming,overloading,type-erasure,Scala,Functional Programming,Overloading,Type Erasure,本质上,我想做的是为一个自定义类编写“map”的重载版本,这样每个版本的map只因传递给它的函数类型不同而不同 这就是我想做的: object Test { case class Foo(name: String, value: Int) implicit class FooUtils(f: Foo) { def string() = s"${f.name}: ${f.value}" def map(func: Int => Int) = Foo(f.name,

本质上,我想做的是为一个自定义类编写“map”的重载版本,这样每个版本的map只因传递给它的函数类型不同而不同

这就是我想做的:

object Test {
  case class Foo(name: String, value: Int)

  implicit class FooUtils(f: Foo) {
    def string() = s"${f.name}: ${f.value}"

    def map(func: Int => Int) = Foo(f.name, func(f.value))
    def map(func: String => String) = Foo(func(f.name), f.value)
  }


  def main(args: Array[String])
  {
    def square(n: Int): Int = n * n
    def rev(s: String): String = s.reverse

    val f = Foo("Test", 3)
    println(f.string)

    val g = f.map(rev)
    val h = g.map(square)
    println(h.string)

  }
}
当然,由于类型擦除,这将不起作用。地图的任何一个版本都可以单独工作,它们可以被命名为不同的版本,并且一切都可以正常工作。但是,非常重要的是,用户可以简单地根据传递给它的函数类型调用正确的map函数

在我寻找如何解决这个问题的过程中,我跨越了类型标签。下面是我提出的代码,我相信它几乎是正确的,但当然不太管用:

import scala.reflect.runtime.universe._

object Test {
  case class Foo(name: String, value: Int)

  implicit class FooUtils(f: Foo) {
    def string() = s"${f.name}: ${f.value}"

    def map[A: TypeTag](func: A => A) =
      typeOf[A] match {
        case i if i =:= typeOf[Int => Int] => f.mapI(func)
        case s if s =:= typeOf[String => String] => f.mapS(func)
      }                                        
    def mapI(func: Int => Int) = Foo(f.name, func(f.value))
    def mapS(func: String => String) = Foo(func(f.name), f.value)
  }


  def main(args: Array[String])
  {
    def square(n: Int): Int = n * n
    def rev(s: String): String = s.reverse

    val f = Foo("Test", 3)
    println(f.string)

    val g = f.map(rev)
    val h = g.map(square)
    println(h.string)

  }
}
当我尝试运行此代码时,会出现以下错误:

[error] /src/main/scala/Test.scala:10: type mismatch;
[error]  found   : A => A
[error]  required: Int => Int
[error]         case i if i =:= typeOf[Int => Int] => f.mapI(func)
[error]                                                      ^
[error] /src/main/scala/Test.scala:11: type mismatch;
[error]  found   : A => A
[error]  required: String => String
[error]         case s if s =:= typeOf[String => String] => f.mapS(func)
func的类型的确是A=>A,那么我如何告诉编译器我在运行时匹配的是正确的类型呢


非常感谢。

在您对
map
的定义中,键入
A
表示函数的参数和结果。
func
的类型是
A=>A
。然后基本上检查一下,例如
typeOf[A]=:=typeOf[Int=>Int]
。这意味着
func
将是
(Int=>Int)=>(Int=>Int)
,这是错误的

使用
TypeTag
s解决此问题的方法之一如下所示:

def map[T, F : TypeTag](func: F)(implicit ev: F <:< (T => T)) = {
  func match {
    case func0: (Int => Int) @unchecked if typeOf[F] <:< typeOf[Int => Int] => f.mapI(func0)
    case func0: (String => String) @unchecked if typeOf[F] <:< typeOf[String => String] => f.mapS(func0)
  }
}
克服方法参数类型擦除的一种更通用的方法是使用。下面是一个工作示例:

sealed trait MapperMagnet {
  def map(foo: Foo): Foo
}
object MapperMagnet {
  implicit def forValue(func: Int => Int): MapperMagnet = new MapperMagnet {
    override def map(foo: Foo): Foo = Foo(foo.name, func(foo.value))
  }
  implicit def forName(func: String => String): MapperMagnet = new MapperMagnet {
    override def map(foo: Foo): Foo = Foo(func(foo.name), foo.value)
  }
}

implicit class FooUtils(f: Foo) {
  def string = s"${f.name}: ${f.value}"

  // Might be simply `def map(func: MapperMagnet) = func.map(f)`
  // but then it would require those pesky underscores `f.map(rev _)`
  def map[T](func: T => T)(implicit magnet: (T => T) => MapperMagnet): Foo = 
    magnet(func).map(f)
}
这是因为当您调用
map
时,隐式
magnet
会在编译时使用完整的类型信息进行解析,因此不会发生擦除,也不需要运行时类型检查

我认为magnet版本更干净,而且作为一个额外的优点,它不使用任何运行时反射调用,您可以调用
map
,参数中没有下划线:
f.map(rev)
,而且它不会抛出运行时匹配错误

更新:

现在我想起来了,这里的magnet实际上并不比一个完整的typeclass简单,但它可能会更好地显示它的意图。不过,这是一种比typeclass更不为人所知的模式。无论如何,下面是使用typeclass模式完整性的相同示例:

sealed trait FooMapper[F] {
  def map(foo: Foo, func: F): Foo
}
object FooMapper {
  implicit object ValueMapper extends FooMapper[Int => Int] {
    def map(foo: Foo, func: Int => Int) = Foo(foo.name, func(foo.value))
  }
  implicit object NameMapper extends FooMapper[String => String] {
    def map(foo: Foo, func: String => String) = Foo(func(foo.name), foo.value)
  }
}

implicit class FooUtils(f: Foo) {
  def string = s"${f.name}: ${f.value}"

  def map[T](func: T => T)(implicit mapper: FooMapper[T => T]): Foo =
    mapper.map(f, func)
}

注意:这里的magnet实际上与完整的typeclass没有什么不同,但可能更好地显示了意图?我发现您的解决方案突出了意图。请您详细解释一下为什么
defmap(func:MapperMagnet)
需要下划线,而选择的方式不需要下划线?@Mik378使用签名
defmap(func:MapperMagnet)
,scala不希望函数作为参数,因此您必须显式地将其扩展为带有下划线的值。只有一个问题:拥有
defmap(func:Int=>Int)=Foo(f.name,func(f.value))defmap(func:String=>String)=Foo(func(f.name),f.value)
,为什么调用时仍然需要传递下划线:
f.map(rev)
。事实上,编译器无法检测到函数应作为输入,然后将其转换(从
def
转换为文字值)隐式?它是重载上下文特有的吗?@Mik378你是什么意思?你评论中的那两个映射由于类型擦除而无法编译。
sealed trait FooMapper[F] {
  def map(foo: Foo, func: F): Foo
}
object FooMapper {
  implicit object ValueMapper extends FooMapper[Int => Int] {
    def map(foo: Foo, func: Int => Int) = Foo(foo.name, func(foo.value))
  }
  implicit object NameMapper extends FooMapper[String => String] {
    def map(foo: Foo, func: String => String) = Foo(func(foo.name), foo.value)
  }
}

implicit class FooUtils(f: Foo) {
  def string = s"${f.name}: ${f.value}"

  def map[T](func: T => T)(implicit mapper: FooMapper[T => T]): Foo =
    mapper.map(f, func)
}