【问题标题】:Scalac cannot infer inductively built path-dependent typeScalac 无法推断归纳构建的路径相关类型
【发布时间】:2017-02-18 14:59:16
【问题描述】:

我正在将 servant-server 端口连接到 Scala。这个想法是使用类型类解析来归纳构建可以处理请求的函数。我遇到了一些我无法弄清楚的奇怪推理问题。

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

}

通过上述,以下编译失败:

val foo = implicitly[HasServer[Int :> Unit]]
implicitly[=:=[Int => String, foo.ServerT]]                                        

错误是:

servant.scala:33: error: 
Cannot prove that Int => String =:= Main.$anon.Servant.foo.ServerT.

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

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

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

如何编译?谢谢!

【问题讨论】:

    标签: scala typeclass implicit


    【解决方案1】:

    问题都出在implicitly的定义中……

    def implicitly[T](implicit e: T) = e
    

    implicitly[T] 只会给你一个键入为T 的值,永远不会更精确。在上面的例子中,HasServer[Int :> Unit] 至关重要的是,它使成员类型 ServerT 不受约束。

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

    object HasServer {
      def apply[T](implicit hs: HasServer[T]):
        HasServer[T] { type ServerT = hs.ServerT } = hs
    }
    

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

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

    无论如何,它可能会在其他地方派上用场。

    我们可以看到这对 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} = ...
    

    这个细化将被推断为 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>
    

    有很多方法可以改进implicitly 的定义以避免这个问题。下面是最直接的引用类型,

    def implicitly[T <: AnyRef](implicit t: T): t.type = t
    

    如果启用了literal types,我们可以删除&lt;: AnyRef 绑定并为所有类型定义它,

    def implicitly[T](implicit t: T): t.type = t
    

    shapeless 提供了一个the[T] 运算符,它通过宏的行为与后者类似。

    【讨论】:

    • 这很有趣,我已经使用 Aux 技巧 2 年了,从不依赖 implicitly,只是意识到隐含地永远不会返回比你所要求的更精确的类型 :) ...一个一天,一种知识;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-17
    • 1970-01-01
    • 2011-04-24
    • 2017-10-13
    • 1970-01-01
    • 2015-12-20
    相关资源
    最近更新 更多