Возможно ли в Scala заставить вызывающую сторону указать параметр типа для полиморфного метода? - PullRequest
14 голосов
/ 10 декабря 2010
//API
class Node
class Person extends Node

object Finder
{
  def find[T <: Node](name: String): T = doFind(name).asInstanceOf[T]
}

//Call site (correct)
val person = find[Person]("joe")

//Call site (dies with a ClassCast inside b/c inferred type is Nothing)
val person = find("joe")

В приведенном выше коде клиентский сайт «забыл» указать параметр типа, так как писатель API хочет , что означает «просто вернуть узел». Есть ли способ определить универсальный метод (не класс) для достижения этого (или эквивалент). Примечание: использование манифеста внутри реализации для выполнения приведения, если (manifest! = Scala.reflect.Manifest.Nothing) не будет компилироваться ... У меня есть неприятное ощущение, что какой-то мастер Scala знает, как использовать Predef. для этого: -)

Идеи?

Ответы [ 4 ]

12 голосов
/ 09 июля 2011

Еще одним решением является указание типа по умолчанию для параметра следующим образом:

object Finder {
   def find[T <: Node](name: String)(implicit e: T DefaultsTo Node): T = 
      doFind(name).asInstanceOf[T]
}

Ключ должен определить следующий тип фантома , который будет выступать в качестве свидетеля по умолчанию:

sealed class DefaultsTo[A, B]
trait LowPriorityDefaultsTo {
   implicit def overrideDefault[A,B] = new DefaultsTo[A,B]
}
object DefaultsTo extends LowPriorityDefaultsTo {
   implicit def default[B] = new DefaultsTo[B, B]
}

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

.

Объяснение

Подпись метода find гарантирует, что он может быть вызван, только если вызывающая сторона может предоставить объект типа DefaultsTo[T, Node]. Конечно, методы default и overrideDefault позволяют легко создать такой объект для любого типа T. Поскольку эти методы неявные, компилятор автоматически обрабатывает вызов одного из них и передачу результата в find.

Но как компилятор узнает, какой метод вызывать? Он использует правила определения типа и неявного разрешения для определения подходящего метода. Есть три случая для рассмотрения:

  1. find вызывается без параметра типа. В этом случае тип T должен быть выведен. В поисках неявного метода, который может предоставить объект типа DefaultsTo[T, Node], компилятор находит default и overrideDefault. default выбран, так как он имеет приоритет (потому что он определен в надлежащем подклассе черты, которая определяет overrideDefault). В результате T должен быть связан с Node.

  2. find вызывается с параметром не Node типа (например, find[MyObj]("name")). В этом случае необходимо указать объект типа DefaultsTo[MyObj, Node]. Только метод overrideDefault может предоставить его, поэтому компилятор вставляет соответствующий вызов.

  3. find вызывается с Node в качестве параметра типа. Опять же, любой метод применим, но default выигрывает благодаря более высокому приоритету.

6 голосов
/ 02 января 2011

Майлз Сабин опубликовал действительно хорошее решение для этой проблемы в списке рассылки scala-user.Определите класс типа NotNothing следующим образом:

sealed trait NotNothing[T] { type U }                                          
object NotNothing {
   implicit val nothingIsNothing = new NotNothing[Nothing] { type U = Any }
   implicit def notNothing[T] = new NotNothing[T] { type U = T }           
}

Теперь вы можете определить Finder как

object Finder {
   def find[T <: Node : NotNothing](name: String): T = 
      doFind(name).asInstanceOf[T]
}

Если вы попытаетесь вызвать Finder.find без параметра типа,вы получите ошибку времени компиляции:

ошибка: неоднозначные неявные значения: оба метода notNothing в объекте $ iw типа [T] java.lang.Object with NotNothing [T] {type U= T} и значение noneIsNothing в объекте $ iw типа => java.lang.Object с NotNothing [Nothing] {type U = Any} соответствует ожидаемому типу NotNothing [T] Finder.find ("joe")

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

5 голосов
/ 10 декабря 2010

Можно получить то, что вы ищете, но это не просто.Проблема заключается в том, что без явного параметра типа компилятор может только определить, что T равно Nothing.В этом случае вы хотите, чтобы find возвращал что-то типа Node, , а не типа T (то есть Nothing), но в любом другом случае вы хотите найти что-то, возвращающее что-то типаT.

Если вы хотите, чтобы ваш возвращаемый тип изменялся в зависимости от параметра типа, вы можете использовать технику, аналогичную той, которую я использовал в моем API для поднятия метода .

object Finder {
   def find[T <: Node] = new Find[T]

   class Find[T <: Node] {
       def apply[R](name: String)(implicit e: T ReturnAs R): R = 
          doFind(name).asInstanceOf[R]
   }

   sealed class ReturnAs[T, R]
   trait DefaultReturns {
      implicit def returnAs[T] = new ReturnAs[T, T]
   }
   object ReturnAs extends DefaultReturns {
      implicit object returnNothingAsNode extends ReturnAs[Nothing, Node]
   }
}

Здесь метод find возвращает полиморфный функтор, который при применении к имени возвращает объект типа T или типа Node в зависимости от значения ReturnAsаргумент предоставлен компилятором.Если T равно Nothing, компилятор предоставит объект returnNothingAsNode, а метод apply вернет Node.В противном случае компилятор предоставит ReturnAs[T, T], а метод apply вернет T.


Отказ от решения Пола в списке рассылки, другая возможность заключается в предоставлении неявного для каждоготипа что "работает".Вместо того, чтобы возвращать Node, когда параметр type не указан, будет выдана ошибка компиляции:

object Finder {
   def find[T : IsOk](name: String): T = 
      doFind(name).asInstanceOf[T]

   class IsOk[T]
   object IsOk {
      implicit object personIsOk extends IsOk[Person]
      implicit object nodeIsOk extends IsOk[Node]
   }
}

Конечно, это решение плохо масштабируется.

1 голос
/ 16 декабря 2010

Решение Пола предоставляет нижнюю границу для T, поэтому val person = find ("joe") - это ошибка времени компиляции, заставляющая вас явно указывать тип (например, Node). Но это довольно ужасное решение (Пол явно сказал, что не рекомендует его), поскольку оно требует, чтобы вы перечислили все свои подтипы.

...