Scalac не может вывести индуктивно построенный тип, зависящий от пути

#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 и только что осознал, что неявно никогда не возвращает более точный тип, чем тот, который вы просите 🙂 … один день, одно знание 😉