#scala #typeclass #implicit
#scala #класс типов #неявный
Вопрос:
Я работаю над портом 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)
}
Как я могу заставить это скомпилировать? Спасибо!
Ответ №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
и если включены литеральные типы, мы могли бы удалить <: AnyRef
привязку и определить ее для всех типов,
def implicitly[T](implicit t: T): t.type = t
shapeless предоставляет the[T]
оператор, который ведет себя аналогично последнему через макрос.
Комментарии:
1. Забавно, я использую вспомогательный трюк уже 2 года и никогда не полагался
implicitly
и только что осознал, что неявно никогда не возвращает более точный тип, чем тот, который вы просите 🙂 … один день, одно знание 😉