В Scala, как мне сказать абстрактному базовому классу, что параметр типа T поддерживает неявное преобразование из Int (или Float, или ...)? - PullRequest
4 голосов
/ 12 февраля 2012

Мне трудно перейти из мира C ++ / Templates в scala. Я привык использовать любую операцию с параметром шаблона T, которую я хочу, при условии, что все, что я использую для создания экземпляра T с, поддерживает эти операции (в основном, типизация Duck во время компиляции). Я не могу найти соответствующую идиому в Scala, которая позволит мне определить абстрактный класс с одним параметром типа и который ожидает определенный интерфейс для типа T.

То, что у меня есть, почти работает, но я не могу понять, как сказать абстрактному классу (Texture [T <: Summable [T]]), что T поддерживает преобразование / конструирование из Int. Как добавить неявное преобразование в черту Summable, чтобы Texture знал, что T поддерживает преобразование? </p>

trait Summable[T] { 
   def += (v : T) : Unit
   def -= (v : T) : Unit   
}

object Int4 { implicit def int2Int4(i : Int) = new Int4(i, i, i, i) }

class Int4 (var x : Int, var y : Int, var z : Int, var w : Int) extends Summable[Int4] {
   def this (v : Int) = this(v, v, v, v)
   def += (v : Int4) : Unit = { x += v.x; y += v.y; z += v.z; w += v.w }
   def -= (v : Int4) : Unit = { x -= v.x; y -= v.y; z -= v.z; w -= v.w } 
}

abstract class Texture[Texel <: Summable[Texel]] {
   var counter : Texel
   def accumulate(v : Texel) : Unit = { counter += v }
   def decrement() : Unit = { counter -= 1 } //< COMPILE ERROR HERE, fails to find implicit
}

class Int4Target extends Texture[Int4] {
   var counter : Int4 = new Int4(0, 1, 2, 3)
}

Ответы [ 3 ]

4 голосов
/ 12 февраля 2012

Вы можете определить неявный параметр конструктора, например, такой:

abstract class Texture[Texel <: Summable[Texel]](implicit int2Texel: Int => Texel) {
//...

По сути, это говорит компилятору, что для создания экземпляра Texture должна существовать неявная функция преобразования, доступная из Intдо Texel.Предполагая, что у вас есть такая функция, определенная где-то в области видимости (что вы и делаете), вы больше не должны получать ошибку компиляции.

Edit2: Хорошо, я изначально неправильно прочитал ваш код, вам фактически нужен только один неявный параметр из Int => Texel.Ваш код компилируется для меня с помощью вышеупомянутой модификации.

Редактировать: Вам действительно понадобятся 2 функции преобразования, одна из Texel => Int и другая из Int => Texel, чтобы правильно переназначить переменную

3 голосов
/ 12 февраля 2012

Принципиальное отличие шаблонов C ++ от всего, что есть в Scala, заключается в том, что шаблоны C ++ компилируются для каждого использования - то есть, если вы используете шаблон с int и с double, то компилируются два разных класса, и они компилируются, только когда какой-то код фактически использует их.

Scala, с другой стороны, имеет отдельную компиляцию. Не так хорошо, как Java, учитывая ограничения JVM, но все еще следуя основному принципу. Итак, если у чего-то есть параметр типа, он все еще компилируется при объявлении, и существует только один такой класс. Этот скомпилированный код должен поддерживать все возможные параметры, чем он может вызываться, что накладывает некоторые другие ограничения, чем шаблоны.

В отношении признаков и неявных преобразований признаки не поддерживают параметры, а неявные преобразования (границы представления) являются параметрами. Вместо этого используйте класс.

2 голосов
/ 12 февраля 2012

В scala невозможно требовать неявного преобразования для типа параметр черты. Для этого есть веская причина. Предположим, мы определили черта типа:

trait ATrait[T <% Int] {
    def method(v: T) { println(v: Int) }
}

А затем сделал это в двух местах:

package place1 {
    implicit def strToInt(s: String) = 5
    val inst = new ATrait[String]
}

package place2 {
    implicit def strToInt(s: String) = 6
    val inst = new ATrait[String]
}

А потом использовал такие экземпляры как:

val a = if (someTest) place1 else place2
a.method("Hello")

Должен ли этот отпечаток 5 или 6? То есть какое неявное преобразование следует использовать? Последствия должны быть найдены во время компиляции, но вы не знаете, какие неявные преобразование присутствовало для создания объекта.

Другими словами, последствия обеспечиваются областью, в которой они используются, а не объектами, на которых они используются; последнее было бы невозможно.

Итак, о вашей проблеме. Вместо использования неявного, вы можете использовать обычный член:

trait Summable[T] { 
   def -= (v: T): Unit
   def -= (v: Int) { this -= (encode(v)) }

   def encode(i: Int): T
}

class Int4 (var x: Int, var y: Int, var z: Int, var w: Int) extends Summable[Int4] {
   def -= (v : Int4) : Unit = { x -= v.x; y -= v.y; z -= v.z; w -= v.w } 

   def encode(i: Int) = Int4.int2Int4(i)
}

Теперь метод decrement компилируется правильно.

Другой способ сказать это - не думать о последствиях как о свойствах, принадлежащих к типу (то есть, «может быть неявно преобразован из Int» не является свойством INT4). Это значения, которые можно идентифицировать с помощью типов.

Надеюсь, это поможет.

...