Scalac无法推断出归纳建立的路径依赖型

aar*_*vin 3 scala implicit typeclass

我正在开发servant-serverScala 的端口.我们的想法是使用类型类分辨率来归纳地构建可以处理请求的函数.我遇到了一些我无法弄清楚的奇怪的推理问题.

object Servant {                                                                     

  class :>[Path, A]                                                                  

  trait HasServer[A] {                                                               
    type ServerT                                                                     

    def route(a: ServerT): String                                                    
  }                                                                                  

  implicit val unitServer = new HasServer[Unit] {                                    
    type ServerT = String                                                            

    def route(str: ServerT): String = str                                            
  }                                                                                  

  implicit def subServer[A, Sub](implicit sub: HasServer[Sub]) = new HasServer[A :> Sub] {
    type ServerT = A => sub.ServerT                                                  

    def route(handler: ServerT): String = "handler"                                  
  } 

}
Run Code Online (Sandbox Code Playgroud)

有了上述内容,以下内容无法编译:

val foo = implicitly[HasServer[Int :> Unit]]
implicitly[=:=[Int => String, foo.ServerT]]                                        
Run Code Online (Sandbox Code Playgroud)

错误是:

servant.scala:33: error: 
Cannot prove that Int => String =:= Main.$anon.Servant.foo.ServerT.
Run Code Online (Sandbox Code Playgroud)

但是,如果我直接实例化它将HasServer[Int :> Unit]通过以下方式编译:

  val foo = new HasServer[Int :> Unit] {                                             
    type ServerT = Int => unitServer.ServerT                                         

    def route(handler: ServerT): String = handler(10)                                
  }
Run Code Online (Sandbox Code Playgroud)

我该如何编译?谢谢!

Mil*_*bin 8

问题在于implicitly...... 的定义

def implicitly[T](implicit e: T) = e
Run Code Online (Sandbox Code Playgroud)

implicitly[T]只会给你一个输入的值T,从来没有更精确.在上面的HasServer[Int :> Unit]例子中,关键的是,这会使成员类型ServerT不受约束.

这通常通过定义每种类型的伴随对象apply方法来解决,该方法保留了期望的细化,例如,

object HasServer {
  def apply[T](implicit hs: HasServer[T]):
    HasServer[T] { type ServerT = hs.ServerT } = hs
}
Run Code Online (Sandbox Code Playgroud)

这里的结果类型有点笨拙,所以将它与"Aux"模式结合起来也很常见,

object HasServer {
  type Aux[T, S] = HasServer[T] { type ServerT = S }
  def apply[T](implicit hs: HasServer[T]): Aux[T, hs.ServerT] = hs
}
Run Code Online (Sandbox Code Playgroud)

在任何情况下,这可能会派上用场.

我们可以看到这与REPL上的推断类型有所不同,

scala> implicitly[HasServer[Int :> Unit]]
res0: Servant.HasServer[Servant.:>[Int,Unit]] = ...

scala> HasServer[Int :> Unit]
res1: Servant.HasServer[Servant.:>[Int,Unit]]{type ServerT = Int => String} = ...
Run Code Online (Sandbox Code Playgroud)

这个改进将被推断为val定义的类型,所以现在你将获得所需的结果,

scala> val foo = HasServer[Int :> Unit]
foo: Servant.HasServer[Servant.:>[Int,Unit]]{type ServerT = Int => String} = ...

scala> implicitly[=:=[Int => String, foo.ServerT]]
res2: =:=[Int => String,foo.ServerT] = <function1>
Run Code Online (Sandbox Code Playgroud)

有许多方法implicitly可以改进定义以避免这个问题.以下是最直接的参考类型,

def implicitly[T <: AnyRef](implicit t: T): t.type = t
Run Code Online (Sandbox Code Playgroud)

如果启用了文字类型,我们可以删除<: AnyRef绑定并为所有类型定义它,

def implicitly[T](implicit t: T): t.type = t
Run Code Online (Sandbox Code Playgroud)

无形的提供了一个the[T]操作符,它通过宏来表现与后者类似的行为.