В Scala, как мне сказать абстрактному базовому классу, что параметр типа T поддерживает неявное преобразование из Int (или Float, или...)?

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

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

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 ответа

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

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

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

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

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

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

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

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

В 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). Это значения, которые можно идентифицировать с помощью типов.

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

Другие вопросы по тегам