Как описать и исправить эту ошибку несоответствия типа Scala?

Ниже приведен конкретный пример ситуации, с которой я иногда сталкиваюсь с параметризованными типами. В принципе, есть параметры типа, которые, как я знаю, совместимы, но я не знаю, как доказать это для некоторых частей кода.

Я пишу маршрутизатор запросов, который сопоставляет URL-адреса с функциями обработчика. Ниже приведен упрощенный код. Я создаю List[Route], где Route - это пара UrlMatcher, Function.

class Route[A](matcher: UrlMatcher[A], handler: HandlerFunction[A])

abstract class UrlMatcher[A] {
   def match(url: String): Option[A]   // None if no match

Параметр типа A предназначен для «аргументов», которые средство сопоставления может извлечь из URL-адреса. Они будут переданы функции-обработчику. Например, UrlMatcher[Int], который видит путь URL-адреса вида «/ users / 123», может передать 123 функции getUser(id: Int). Маршрутизатор может выглядеть так:

val routes = ArrayBuffer[Route[_]]

def callHandler(url: String) {
  for (r <- routes) {
    val args = r.matcher.matchUrl(url)
    if (args.isDefined)
      r.handler(args.get)  // <--- error here
    }

Проблема в том, что я получаю ошибки несоответствия типов, потому что не знаю, как сказать, что эти два типа одинаковы.

type mismatch; found: args.type (with underlying type Option[Any])  
            required: _$1  

Я знаю, что могу переделать его так, чтобы Route имел такой метод, как matchAndCall, но я бы хотел сохранить этот логический поток, если это возможно.

Обновить / изменить

Я не совсем понимаю экзистенциальные типы, но я пробовал это ...

val routes = ArrayBuffer[T forSome { type T }]()

И это устранило ошибку несоответствия выше. Однако у меня есть еще один, куда я вставлял ArrayBuffer.

def route[P](matcher: UrlMatcher[P], handler: Handler[P]): AbstractRoute = {
  val route = new Route(matcher, handler)
  otherRoutes.append(route)   // error here  
  route
}

Теперь ошибка ...

type mismatch;  found : Route[P]  required: Route[T forSome { type T }] Note: P <: T
forSome { type T }, but class Route is invariant in type P. You may wish to define 
P as +P instead. (SLS 4.5) 

Почему P несовместимо с T, поскольку они не ограничивают T?


person Rob N    schedule 13.09.2013    source источник


Ответы (1)


Это одна из причин, по которой экзистенциальные типы (эквивалент подстановочных знаков в Scala) являются плохой вещью (TM), которой лучше избегать, когда не выполняется взаимодействие с Java: компилятор не может (или просто недостаточно умен, чтобы) обычно рассуждать о том, какой типы равны которым, потому что все они ушли ...

Чтобы компилятор понял, что эти типы одинаковы, вам нужно как-то дать этому типу имя.

Параметр типа - это возможность: вы определяете параметризованный метод с содержимым вашего для понимания, чтобы внутри метода тип был хорошо известен.

def callHandler(url: String) {
  def call[T](r: Route[T]) = {
    val args = r.matcher.matchUrl(url)
    if (args.isDefined) r.handler(args.get)
    // or args.foreach(r.handler)
  }
  for (r <- routes) call(r)
  // or routes.foreach(call)
}

Примечание: в более простых случаях вы также можете использовать дисперсию, чтобы получить List[Route[Any]], и ваша проблема исчезнет, ​​тип снова хорошо известен. Я не уверен, что вы можете сделать Route[A] ковариантным.

Экзистенциальные типы в основном существуют для представления подстановочных знаков Java, исходных типов Java и представления типов JVM (отражение и т. ), хотя они более мощные, чем эти три конструкции. Если вы можете спроектировать вещи, избегая их использования, вы сэкономите себе много боли. Это было немного спорно, но, по крайней мере, есть много ограничений на то, как они взаимодействуют с выводом типов , поэтому нужно быть осторожным.

person gourlaysama    schedule 13.09.2013
comment
Я понимаю настроения здесь, но это просто неправда, что экзистенциальные типы Scala только для взаимодействия с Java (Scala тратит целое ключевое слово - а у него не так много - например, на forSome) . Существует множество полностью законных (хотя и более продвинутых) способов использования, которые не имеют ничего общего с Java. - person Travis Brown; 13.09.2013
comment
Да, они строго более мощные, чем подстановочные знаки Java и тому подобное (и намного старше), но я помню, как Мартин Одерский где-то говорил, что их не было бы в Scala, если бы не совместимость со стиранием, подстановочными знаками и необработанными типами. - person gourlaysama; 13.09.2013
comment
Я не понимаю экзистенциальных типов, таких как значение [T <: Foo] vs [T forSome { type T <: Foo }], но я обновил вопрос, добавив другой подвопрос. Я, вероятно, воспользуюсь вашим предложением здесь и определю вспомогательный метод. Спасибо. - person Rob N; 13.09.2013