克服高阶函数函数变量的Scala型擦除

Jac*_*nig 2 functional-programming scala 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, 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)

  }
}
Run Code Online (Sandbox Code Playgroud)

当然,由于类型擦除,这是行不通的.任何一个版本的地图都可以单独使用,它们的名称可以不同,一切正常.但是,用户只需根据传递给它的函数类型调用正确的映射函数,这一点非常重要.

在我寻找如何解决这个问题的过程中,我想到了TypeTags.这是我提出的代码,我认为它接近正确,但当然不太有效:

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)

  }
}
Run Code Online (Sandbox Code Playgroud)

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

[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)
Run Code Online (Sandbox Code Playgroud)

确实func的类型为A => A,那么如何告诉编译器我在运行时匹配正确的类型呢?

非常感谢你.

Kol*_*mar 6

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

使用TypeTags 修复此问题的方法之一如下:

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

你必须用下划线称它为:f.map(rev _).它可能会引发匹配错误.

有可能改进这个代码,但我建议做一些更好的事情.克服重载方法参数上的类型擦除的最简单方法是使用DummyImplicit.只需DummyImplicit为一些方法添加一个或多个隐式参数:

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

  def map(func: Int => Int)(implicit dummy: DummyImplicit) = Foo(f.name, func(f.value))
  def map(func: String => String) = Foo(func(f.name), f.value)
}
Run Code Online (Sandbox Code Playgroud)

克服方法参数类型擦除的更一般方法是使用磁体模式.这是一个有效的例子:

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)
}
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为在调用时map,magnet使用完整类型信息在编译时解析隐式,因此不会发生擦除,也不需要运行时类型检查.

我认为磁体版本更干净,作为奖励它不使用任何运行时反射调用,你可以map在参数中没有下划线调用:f.map(rev),并且它也不能抛出运行时匹配错误.

更新:

现在我想到了,这里的磁铁并不比完整的类型类更简单,但它可能会更好地显示出意图.虽然这是一个鲜明的模式而不是类型类.无论如何,这是使用类型类模式完整性的相同示例:

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)
}
Run Code Online (Sandbox Code Playgroud)