Scalac 无法推断归纳构建的路径相关类型
Scalac cannot infer inductively built path-dependent type
我正在开发 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)
}
我怎样才能让它编译?谢谢!
问题都在implicitly
的定义...
def implicitly[T](implicit e: T) = e
implicitly[T]
只会给你一个输入为 T
的值,永远不会更精确。在上面的例子中,那是 HasServer[Int :> Unit]
,关键是,它使成员类型 ServerT
不受约束。
这通常通过定义每个类型 class 伴随对象 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 我们可以删除 <: AnyRef
绑定并为所有类型定义它,
def implicitly[T](implicit t: T): t.type = t
shapeless 提供了一个 the[T]
运算符,它通过宏与后者的行为类似。
我正在开发 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)
}
我怎样才能让它编译?谢谢!
问题都在implicitly
的定义...
def implicitly[T](implicit e: T) = e
implicitly[T]
只会给你一个输入为 T
的值,永远不会更精确。在上面的例子中,那是 HasServer[Int :> Unit]
,关键是,它使成员类型 ServerT
不受约束。
这通常通过定义每个类型 class 伴随对象 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 我们可以删除 <: AnyRef
绑定并为所有类型定义它,
def implicitly[T](implicit t: T): t.type = t
shapeless 提供了一个 the[T]
运算符,它通过宏与后者的行为类似。