Я прихожу из C ++ и пытаюсь обернуть голову вокруг системы типов scala.
Рассмотрим следующий шаблонный класс C ++:
template<class T>
class Point2
{
Point2( T x, T y ) :
x(x),
y(y)
{}
T x;
T y;
Point2<T> operator+( Point<T> const& other ) const
{
return Point<T>(x+other.x, y+other.y);
}
T sumComponents() const { return x+y; }
}
Point2<Double> p0(12.3, 45.6)
Point2<Double> p1(12.3, 45.6)
Point2<Double> p = p1+p2
Double d = p1.sumComponents()
Я обнаружил, что хочу написать что-то вроде этого:
case class Point2[ T ] (x:T, y:T) {
def +() Point2[T]: = x+y
def sumComponents() T: = x+y
}
или (потому что у компиляции есть проблемы с этим),
trait Addable[T] { // Require T supports the + operatory
def +( that:T ):T
}
case class Point2[ T<:Addable[T] ] (x:T, y:T) {
def +() Point2[T]: = x+y
def sumComponents() T: = x+y
}
что также проблематично, потому что я не могу требовать, чтобы Double расширял Addable.
Как правило, я считаю, что система типов scala работает с набором ограничений, которые я не совсем понимаю.
Какой идиоматический способ реализации вышесказанного в Scala?
И как правильно программистам шаблонов C ++ понять границы обобщений в scala? (Зачем я не могу сделать это в скале? например Это потому, что дженерики скомпилированы до того, как будут созданы?)
Я создал библиотеку template.scala. Вы можете использовать библиотеку для создания шаблонов со вкусом C ++, избегая сложных implicit
s.
import com.thoughtworks.template
case class Point2[T](x:T, y:T) {
@template def +(rhs: Point2[_]) = Point2(x + rhs.x, y + rhs.y)
@template def sumComponents() = x + y
}
println(Point2(1, 3).sumComponents()) // Output: 4
println(Point2(1, 3) + Point2(100, 200)) // Output: Point2(101,203)
Обратите внимание, что вы можете даже плюс два Point2
с различными типами компонентов.
println(Point2(1.5, 0.3) + Point2(100, 200)) // Output: Point2(101.5,200.3)
Даже вложенный Point2
:
// Output: Point2(Point2(10.1,20.2),Point2(101.0,202.0))
println(Point2(Point2(0.1, 0.2), Point2(1.0, 2.0)) + Point2(Point2(10, 20), Point2(100, 200)))
Это работает потому что @template
функции — это шаблоны кода, которые будут встроены в сайт вызова.
Какой идиоматический способ реализации вышесказанного в Scala?
Либо, указав соответствующие требования T
или используя классы типов для обеспечения желаемого поведения. Я вернусь к этому позже.
И как правильно понимать шаблонизаторы C ++?
пределы дженериков в скале? (Почему я не могу сделать это в Scala? Например.
Это потому, что дженерики скомпилированы до того, как будут созданы?)
Шаблоны C ++ компилируются «на» сайте использования, и для каждой комбинации параметров в шаблонах генерируется различный код. Так что если вы используете класс выше с int
а также double
получаешь два разных Point2
классы скомпилированы.
По сути, шаблоны C ++ являются макросами, хотя и далеко не так глупы, как #define
макросы. На самом деле шаблоны C ++ завершены. Возможно, в будущем удастся добиться чего-то эквивалентного с запланированными возможностями макросов для Scala 2.11 и более поздних версий, но давайте пока проигнорируем это.
Параметры типа (эквивалент Scala Java дженерики) не меняйте способ компиляции кода. Параметризованный класс генерирует свой байт-код при компиляции, а не при использовании. Итак, к тому времени, когда один экземпляр Point2
с Double
, слишком поздно для генерации байт-кода.
Это означает, что код, сгенерированный параметризованным классом, должен быть совместим со всеми типами, с которыми может быть создан экземпляр класса.
И это источник проблемы: любые методы, вызываемые T
должно быть известно, чтобы присутствовать на T
в это время Point2
компилируется. Следовательно, T
должны быть определены, чтобы иметь верхнюю границу признаков или классов, которые определяют такие методы, как вы показали в своем примере.
Конечно, это не всегда возможно, как вы правильно отметили, и именно здесь приходят классы типов. Класс типов — это набор типов, для которых определен набор поведений. Классы типов, реализованные в Scala, определяются как классы, экземпляры которых определяют поведение других классов.
В приведенном вами примере вы будете использовать либо Numeric
тип класса или Fractional
введите class, если вам нужно дробное деление. Простой пример использования класса типов:
scala> import scala.math.Numeric
import scala.math.Numeric
scala> def sum[T](x: T, y: T)(implicit num: Numeric[T]): T = num.plus(x, y)
sum: [T](x: T, y: T)(implicit num: scala.math.Numeric[T])T
Или, используя специальную запись, называемую «границы контекста»,
scala> def sum[T : Numeric](x: T, y: T): T = implicitly[Numeric[T]].plus(x, y)
sum: [T](x: T, y: T)(implicit evidence$1: scala.math.Numeric[T])T
Запись T : Numeric
можно прочитать как T
такой, что есть неявный случай Numeric[T]
имеется в наличии. Код implicitly[X]
возвращает неявное значение типа X
если можно найти (или не удается во время компиляции).
Теперь обратите внимание, как не вызывается метод x
а также y
— вместо этого мы вызываем методы num
чей класс Numeric[T]
, Класс Numeric[T]
есть метод plus
который знает, как добавить два T
s.
Поскольку нам нужны экземпляры классов типов, можно легко добавлять новые типы для удовлетворения класса типов. Можно легко объявить Numeric
тип класса для Point2
(при условии, что все его методы могут быть реализованы):
class Point2Numeric[T](implicit num: Numeric[T]) extends Numeric[Point2[T]] {
def plus(x: Point2[T], y: Point2[T]): Point2[T] = x + y
// etc
}
implicit def ToPoint2Numeric[T : Numeric] = new Point2Numeric[T]
С этим на месте, то для любого T
для которого есть Numeric[T]
было бы также Numeric[Point2[T]]
,
После простого наследования типов (верхние границы типов) классы типов являются наиболее распространенной формой ограничения типов, используемой в Scala. Существуют и другие, более сложные формы, для которых есть некоторое обсуждение, являются ли они классами типов или чем-то другим — например, модель магнита. смотреть на бесформенный для примера того, как далеко можно взять такие вещи.
Другой тип ограничения типа, который раньше был очень распространенным, но теперь используется более осмотрительно, смотреть границы. Я не буду вдаваться в подробности (на самом деле, поиск границ контекста и просмотр границ, чтобы найти длинный ответ об этом от себя), но их можно использовать, чтобы сделать классы типов более читабельными при использовании. Например:
scala> import scala.math.Numeric.Implicits._
import scala.math.Numeric.Implicits._
scala> def sum[T : Numeric](x: T, y: T): T = x + y
sum: [T](x: T, y: T)(implicit evidence$1: scala.math.Numeric[T])T
Импортированные определения содержат неявные преобразования, которые позволяют использовать значения типа T
для которого есть Numeric[T]
как если бы они сами имели такие методы, как +
или же -
,
В заключение отметим, что важно понимать, что это проходит через многие уровни косвенности и, следовательно, может не очень подходить для высокопроизводительного кода.
Просто вы можете сделать что-то вроде этого:
scala> :paste
// Entering paste mode (ctrl-D to finish)
import math.Numeric
import math.Numeric.Implicits._
case class Point2[A: Numeric](x: A, y: A) {
def + (other: Point2[A]): Point2[A] =
Point2(this.x + other.x, this.y + other.y)
def sumComponents: A = x + y
}
// Exiting paste mode, now interpreting.
import math.Numeric
import math.Numeric.Implicits._
defined class Point2
scala> val p1 = Point2(1, 2)
p1: Point2[Int] = Point2(1,2)
scala> val p2 = Point2(3, 4)
p2: Point2[Int] = Point2(3,4)
scala> p1 + p2
res2: Point2[Int] = Point2(4,6)
scala> val p3 = Point2(1.2, 3.4)
p3: Point2[Double] = Point2(1.2,3.4)
scala> val p4 = Point2(1.6, 6.4)
p4: Point2[Double] = Point2(1.6,6.4)
scala> p3 + p4
res3: Point2[Double] = Point2(2.8,9.8)
scala>
Это требует класса типа (который я называю Addition
) и неявное преобразование (которое я определяю через неявный класс под названием Op
). На практике вы бы использовали Numeric
введите для этой конкретной ситуации, но с целью иллюстрации, это, как вы бы определили свой собственный:
trait Addition[T] {
def add(a: T, b: T): T
implicit class Op(a: T) {
def +(b: T) = add(a, b)
}
}
implicit object IntAddition extends Addition[Int] {
def add(a: Int, b: Int) = a + b
}
implicit object DoubleAddition extends Addition[Double] {
def add(a: Double, b: Double) = a + b
}
case class Point2[T](x: T, y: T)(implicit addition: Addition[T]) {
import addition.Op
def +(p: Point2[T]): Point2[T] = Point2(x + p.x, y + p.y)
def sumComponents(): T = x + y
}
использование Numeric
, доступно как implicit
,
import scala.math.Numeric;
case class Point2[T](x: T, y: T)(implicit num: Numeric[T])
смотреть на Numeric
в API, делает то, что вам нужно