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

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

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

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 метод?

3 ответа

Решение

Использование неявного метода

Наиболее распространенным и общим подходом является использование неявного метода, определенного в Predef:

def add[T: Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x,y)

Очевидно, это несколько многословно и требует повторения имени класса типа.

Ссылка на параметр доказательства (не!)

Другой альтернативой является использование имени параметра неявного доказательства, автоматически сгенерированного компилятором:

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)

По крайней мере, начиная с 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

Этот ответ описывает другой подход, который приводит к более читаемому, самодокументируемому клиентскому коду.

мотивация

context Метод, который я описал ранее, является очень общим решением, которое работает с любым типом класса, без каких-либо дополнительных усилий. Однако это может быть нежелательно по двум причинам:

  • context Метод нельзя использовать, когда параметр типа имеет несколько границ контекста, поскольку у компилятора нет способа определить, какая граница контекста предназначена.

  • Ссылка на родовой context Метод вредит читабельности клиентского кода.

Типоспецифичные методы

Использование метода, привязанного к нужному классу типов, делает код клиента намного более читабельным. Этот подход используется в стандартной библиотеке для класса типов Manifest:

// 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] }

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

def numeric = Implicitly[Numeric]
// or
val numeric = Implicitly[Numeric]

Наконец, клиентский код может неявно использовать следующее:

def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y)
Другие вопросы по тегам