Как получить экземпляр класса типа, связанного с привязкой к контексту?

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

Рассмотрим следующий простой способ:

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)