与类型的反射

时间:2013-08-02 14:59:01

标签: scala generics reflection typeclass

我正在为一些应用程序编写一个小注册模块,并使用类型类来执行此操作。主要逻辑基于喷涂路由:

val api = post {
    path("register" / Rest) { rest =>
      rest match {
        case "user" => register[User]
        // ... other cases
      }
    }
  }
}

为了简化,我有以下用户数据类型:

case class User(id: String, email: String)

要从POST请求中提取实体,我正在使用类型类:

trait EntityExtractor[T] {
  def extractFromRequest: Directive[T :: String :: HNil]
}

用户实施:

object User {
  implicit val extractor = new EntityExtractor[User] {
    def extractFromRequest: Directive[User :: String :: HNil] =
      formFields('email, 'pass).hmap {
        case email :: pass :: HNil =>
          val id = UID.genId
          User(id, email) :: pass :: HNil
      }
  }
}

我的register方法出现了问题,该方法使用了类型类指令:

def register[T] =
  extractEntity[T].apply { entity => // fails here
    validateEntity(entity) {
      completeRegistration(entity)
    }
  }
}

def extractEntity[T: EntityExtractor]: Directive[Entity[T] :: HNil] = {
  implicitly[EntityExtractor[T]].extractFromRequest.hmap {
    case entity :: pass :: HNil => Entity(entity, pass) :: HNil
  }
}

它失败并出现例外:could not find implicit value for evidence parameter of type EntityExtractor[T]

有没有办法用反射来解决这个问题(TypeTags或Manifest)没有模式匹配

1 个答案:

答案 0 :(得分:0)

希望以下有些人为的例子能够澄清整个问题。

考虑这段代码(与你当前的代码相对应):

object Main {
  def main(args: Array[String]) {
    implicit val intIntable = new Intable[Int] {
      def toInt(obj: Int) = obj
    }

    println(squareAndDouble(10: Int))
  }

  // Corresponds to your register[T] function
  def squareAndDouble[T](obj: T) = {
    val d = double[T](obj)
    d*d
  }

  // Corresponds to your extractEntity[T] function
  def double[T: Intable](obj: T) = {
    implicitly[Intable[T]].toInt(obj)*2
  }
}

trait Intable[T] {
  def toInt(obj: T): Int
}

此代码无法编译时出现以下错误:

test.scala:11: error: could not find implicit value for evidence parameter of type Intable[T]
    val d = double[T](obj)

现在让我们使用隐式参数重写它(这是编译器在看到上下文绑定时为我们做的事情):

object Main {
  def main(args: Array[String]) {
    implicit val intIntable = new Intable[Int] {
      def toInt(obj: Int) = obj
    }

    println(squareAndDouble(10: Int))
  }

  def squareAndDouble[T](obj: T) = {  
    val d = double[T](obj)
    d*d
  }


  def double[T](obj: T)(implicit intable: Intable[T]) = {
    intable.toInt(obj)*2
  }
}

trait Intable[T] {
  def toInt(obj: T): Int
}

此程序也不编译:

test.scala:11: error: could not find implicit value for parameter intable: Intable[T]
    val d = double[T](obj)

现在应该更清楚为什么它不能编译。它失败只是因为T函数内squareAndDouble函数内的square类型参数没有隐式值('证据')。double函数需要它。由于squareAndDouble函数需要其类型参数的证据(可以是任何内容),此证据在squareAndDouble内的范围内出现的唯一方法是再次来自隐式参数,但现在在{{1}中功能。

基本上,这意味着您必须“传递”通用参数的证据才能使用类型类。

让我们解决它:

object Main {
  def main(args: Array[String]) {
    implicit val intIntable = new Intable[Int] {
      def toInt(obj: Int) = obj
    }

    println(squareAndDouble(10: Int))
  }

  // Added context bound here
  def squareAndDouble[T: Intable](obj: T) = {
    val d = double[T](obj)
    d*d
  }

  def double[T: Intable](obj: T) = {
    implicitly[Intable[T]].toInt(obj)*2
  }
}

trait Intable[T] {
  def toInt(obj: T): Int
}

现在它编译并成功运行:

% scalac test.scala
% scala Main
400

另一种(可能更简单)理解问题的方法如下。我们的squareAndDouble[T]函数接受无界类型参数T,但随后它尝试调用另一个函数double[T: Intable],其类型参数有界。这是不允许的:如果它是合法的,那么就可以用任何类型调用squareAndDouble[T],即使没有Intable类型类实例的那个,这也会因此而破坏{{1}功能。因此,我们必须将double边界添加到Intable函数以便编译:{{1​​}},然后它才能完美运行。
这实际上非常类似于使用类继承而不是隐式值的上/下边界。