Как убедиться, что у некоторого типа есть определенный член - PullRequest
3 голосов
/ 06 сентября 2011

Я ищу способ ограничить мой полиморфный класс типами, которые имеют определенную функцию-член.

class Table[T](bla: Array[T]) {
  val symbols = bla

  symbols.foreach( x => x * probability(x))

  def probability(t: T) : Double =  ...
}   

Этот код не компилируется, поскольку T не имеет члена *.Как я могу заверить это.Я не хочу использовать наследование.

Редактировать: вероятность фактически реализована.Возвращает Double.

Есть идеи?

Ответы [ 4 ]

3 голосов
/ 06 сентября 2011

Проблема может быть решена разными способами. Например, если вы просто хотите, чтобы тип T имел какой-либо метод (и вам все равно, определен ли этот метод для объекта или есть неявное преобразование, которое покрывает объект чем-то, что имеет этот метод), тогда вы можете использовать просмотр границ . Вот пример, который ожидает, что тип T будет иметь метод def *(times: Int): T:

class Table[T <% {def *(times: Int): T}](bla: Array[T]) {
  bla.foreach( x => println(x * 2))
} 

new Table(Array("Hello", "World"))
// Prints:
//   HelloHello
//   WorldWorld

String не имеет метода *, но существует неявное преобразование в StringOps с этим методом.

Вот еще один пример. В этом случае я ограничил тип T методом def size: Int:

class Table[T <% {def size: Int}](bla: Array[T]) {
  bla.foreach( x => println(x.size))
} 

new Table(Array(List(1, 2, 3), List("World")))
// Prints:
//  3
//  1

List имеет метод size, и он также работает как ожидалось.

Но это может быть более сложным, если вы работаете с числовыми значениями, такими как int, float, double и т. Д. В этом случае я могу порекомендовать вам использовать context bound . Scala имеет числовой класс типа. Вы можете использовать его для работы с числами, не зная об их типе (с Numeric вы можете работать с любым, что может быть представлено как число, поэтому ваш код будет гораздо более общим и абстрактным). Вот пример, если это:

import math.Numeric.Implicits._

class Table[T : Numeric](bla: Array[T]) {
  bla.foreach( x => println(x * x))
} 

new Table(Array(1, 2, 3))
// Prints:
//  1
//  4
//  9

new Table(Array(BigInt("13473264523654723574623"), BigInt("5786785634377457457465784685683746583454545454")))
// Prints:
//  181528856924372945350108280958825119049592129
//  33486887978237312740760811863500355048015109407078304275771413678604907671187978933752066116

Обновление

Как вы отметили в комментариях, Numeric все еще не решит вашу проблему, потому что он может работать только на числах того же типа. Вы можете просто решить эту проблему, введя новый класс типов. Вот пример этого:

import math.Numeric.Implicits._

trait Convert[From, To] {
    def convert(f: From): To
}

object Convert {
    implicit object DoubleToInt extends Convert[Double, Int] {
        def convert(d: Double): Int = d.toInt
    }

    implicit object DoubleToBigInt extends Convert[Double, BigInt] {
        def convert(d: Double): BigInt = d.toLong
    }
}

type DoubleConvert[To] = Convert[Double, To]

class Table[T : Numeric : DoubleConvert](bla: Array[T]) {
  bla.foreach( x => println(x * implicitly[DoubleConvert[T]].convert(probability(x))))
  def probability(t: T) : Double = t.toDouble + 2.5
} 

new Table(Array(1, 2, 3))
new Table(Array(BigInt("13473264523654723574623"), BigInt("5786785634377453434")))

С классом типа DoubleConvert и контекстом T : Numeric : DoubleConvert вы не только говорите, что T должно быть каким-то числом, но также должно существовать некоторое доказательство того, что оно может быть преобразовано из Double , Вы получаете такое свидетельство с implicitly[DoubleConvert[T]], а затем используете его для преобразования Double в T. Я определил Convert для Double -> Int и Double -> BigInt, но вы также можете определить свои собственные для нужных вам типов.

1 голос
/ 07 сентября 2011

Все остальные отвечают «Структурные типы». Совершенно верно, потому что это правильный ответ!

Вместо того, чтобы повторять очевидное, я остановлюсь на этом. Взяв фрагмент из ответа Easy Angel:

class Table[T <% {def *(times: Int): T}](bla: Array[T]) {
  bla foreach {x => println(x*2)}
} 

Если вы обнаружите, что используете одно и то же выражение {def *(times: Int): T} более одного раза, вы можете создать псевдоним типа для него

type HasTimes = {def *(times: Int): T}

class Table[T <% HasTimes](bla: Array[T]) {
  bla foreach {x => println(x*2)}
} 
1 голос
/ 06 сентября 2011

Используйте структурную типировку scala: http://markthomas.info/blog/?p=66

Ваш код будет выглядеть примерно так:

class Table[T <: {def *(i:Int): T}](bla: Array[T]) {
    ...
}
0 голосов
/ 06 сентября 2011

Если вы не хотите использовать наследование, единственным другим ограничением, которое вы можете применить, является ограничение контекста. Поэтому, если у вас есть список классов, которые вам подойдут, вы создаете implicit object HasStar[X] для каждого класса X и используете контекст, связанный с T:HasStar. Я знаю, что это, вероятно, не совсем то, что вы хотите, но я не думаю, что есть лучшие варианты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...