Как получить экземпляр класса типа, связанный с контекстным ограничением? - PullRequest
15 голосов
/ 07 декабря 2010

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

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

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

Iможно переписать это, используя привязанный к контексту следующим образом

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

, но как мне получить экземпляр типа Numeric[T], чтобы я мог вызвать метод plus?

Ответы [ 3 ]

24 голосов
/ 07 декабря 2010

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

Наиболее распространенным и общим подходом является использование неявно метода , определенного в 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)
7 голосов
/ 21 мая 2011

По крайней мере, начиная с 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
4 голосов
/ 14 июля 2011

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

Мотивация

Метод 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)
...