1

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

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

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

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

为了从 POST 请求中提取实体,我使用了 typeclass:

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
      }
  }
}

问题出现在我使用 typeclass 指令的 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)来解决这个问题?

4

1 回答 1

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类型参数的隐式值(“证据”) 。由于函数需要其类型参数的证据(可以是任何东西),因此该证据出现在内部范围内的唯一方法是再次来自隐式参数,但现在在函数中。squareAndDoublesquaredoublesquareAndDoublesquareAndDouble

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

让我们修复它:

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]使用任何类型调用,即使是没有Intabletypeclass 实例的类型,这将因此破坏double函数。因此,我们也必须为函数添加Intable边界才能squareAndDouble编译:squareAndDouble[T: Intable],然后它才能完美运行。
这实际上非常类似于使用类继承而不是隐式值的上限/下限。

于 2013-08-02T19:45:13.393 回答