Как получить экземпляр класса типа, связанного с привязкой к контексту?
Примечание. Я задаю этот вопрос, чтобы ответить на него сам, но другие ответы приветствуются.
Рассмотрим следующий простой способ:
def add[T](x: T, y: T)(implicit num: Numeric[T]) = num.plus(x,y)
Я могу переписать это с помощью контекстной привязки следующим образом
def add[T: Numeric](x: T, y: T) = ??.plus(x,y)
но как мне получить экземпляр типа Numeric[T]
, чтобы я мог вызвать метод plus
?
Ответы
Ответ 1
Использование неявного метода
Наиболее распространенный и общий подход заключается в использовании метода неявно, определенного в Predef:
def add[T: Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x,y)
Очевидно, что это несколько подробный и требует повторения имени класса type.
Ссылка на параметр доказательств (не надо!)
Другой альтернативой является использование имени параметра неявного доказательства, автоматически генерируемого компилятором:
def add[T: Numeric](x: T, y: T) = evidence$1.plus(x,y)
Удивительно, что этот метод является даже законным, и на практике его нельзя использовать, поскольку имя параметра доказательства может измениться.
Контекст более высокого качества (вводя метод context
)
Вместо этого можно использовать расширенную версию метода implicitly
. Обратите внимание, что неявный метод определяется как
def implicitly[T](implicit e: T): T = e
Этот метод просто полагается на компилятор, чтобы вставить неявный объект правильного типа из окружения в вызов метода, а затем возвращает его. Мы можем сделать немного лучше:
def context[C[_], T](implicit e: C[T]) = e
Это позволяет нам определить наш метод add
как
def add[T: Numeric](x: T, y: T) = context.plus(x,y)
Параметры типа context
типа Numeric
и T
выводятся из области действия! К сожалению, есть обстоятельства, при которых этот метод context
не будет работать. Когда параметр типа имеет несколько границ контекста или, например, есть несколько параметров с различными границами контекста. Мы можем решить последнюю проблему с чуть более сложной версией:
class Context[T] { def apply[C[_]]()(implicit e: C[T]) = e }
def context[T] = new Context[T]
В этой версии мы должны указывать параметр типа каждый раз, но обрабатываем несколько параметров типа.
def add[T: Numeric](x: T, y: T) = context[T]().plus(x,y)
Ответ 2
По крайней мере, с Scala 2.9 вы можете сделать следующее:
import Numeric.Implicits._
def add[T: Numeric](x: T, y: T) = x + y
add(2.8, 0.1) // res1: Double = 2.9
add(1, 2) // res2: Int = 3
Ответ 3
Этот ответ описывает другой подход, который приводит к более читабельному самодокументируемому клиентскому коду.
Мотивация
Метод context
, который я описал ранее, является очень общим решением, которое работает с любым типом класса без каких-либо дополнительных усилий. Однако это может быть нежелательным по двум причинам:
-
Метод context
не может использоваться, когда параметр типа имеет несколько границ контекста, поскольку компилятор не имеет возможности определить, какая граница контекста предназначена.
-
Ссылка на общий метод context
вредит читабельности кода клиента.
Методы, специфичные для класса
Использование метода, привязанного к требуемому типу, делает код клиента более читаемым. Это подход, используемый в стандартной библиотеке для класса типа манифеста:
// definition in Predef
def manifest[T](implicit m: Manifest[T]) = m
// example usage
def getErasure[T: Manifest](x: T) = manifest[T].erasure
Обобщение этого подхода
Основной недостаток использования методов, специфичных для типа, заключается в том, что для каждого класса классов должен быть определен дополнительный метод. Мы можем облегчить этот процесс следующими определениями:
class Implicitly[TC[_]] { def apply[T]()(implicit e: TC[T]) = e }
object Implicitly { def apply[TC[_]] = new Implicitly[TC] }
Затем для любого типа класса может быть определен новый тип неявного стиля типа type:
def numeric = Implicitly[Numeric]
// or
val numeric = Implicitly[Numeric]
Наконец, код клиента может использовать Неявно следующим образом:
def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y)