peri4n
peri4n

Reputation: 1421

How to assure that some type has a specific member

I am searching for a way to restrict my polymorphic class to types that have a certain member function.

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

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

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

This code does not compile because T doesnt have member *. How can I assure this. I dont want to use inheritance.

Edit: probability is actually implemented. It returns a Double.

Any ideas?

Upvotes: 3

Views: 1338

Answers (4)

Kevin Wright
Kevin Wright

Reputation: 49705

Everyone else is answering with "Structural Types". Quite rightly so, because that's the correct answer!

Instead of repeating the obvious, I'll expand upon it. Taking a snippet from Easy Angel's reply:

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

If you find you're using the same expression {def *(times: Int): T} more than once, then you can create a type alias for it

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

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

Upvotes: 1

tenshi
tenshi

Reputation: 26586

The problem can be solved in different ways. For example, if you just want type T to have some method (and you don't care whether this method defined on the object or there is implicit conversion that coverts object to something that has this method), then you can use view bounds. Here is an example that expects type T to have method 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 does not have method *, but there exist an implicit conversion to StringOps that has this method.

Here is another example. In this case I restricting type T with method 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 has method size, and it also works as expected.

But this could be more involving if you are working with numeric values like ints, floats, doubles, etc. In this case I can recommend you to use context bound. Scala has Numeric type class. You can use it to work with numbers without knowledge about their type (with Numeric you can actually work with anything that can be represented as number, so your code would be much more general and abstract). Here is an example if it:

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

Update

As you noted in comments, Numeric still does not solve your problem, because it can only work on the numbers of the same type. You can simply solve this problem by introducing new type class. Here is an example of it:

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")))

With DoubleConvert type class and T : Numeric : DoubleConvert context bound you are not only saying, that T should be some kind of number, but also that there should exist some evidence, that it can be converted from Double. You are receiving such evidence with implicitly[DoubleConvert[T]] and then you are using it to convert Double to T. I defined Convert for Double -> Int and Double -> BigInt, but you can also define you own for the types you need.

Upvotes: 3

sblundy
sblundy

Reputation: 61434

Use scala's structural typing: http://markthomas.info/blog/?p=66

Your code will end up looking something like this:

class Table[T <: {def *(i:Int): T}](bla: Array[T]) {
    ...
}

Upvotes: 1

Kim Stebel
Kim Stebel

Reputation: 42045

If you don't want to use inheritance, the only other restriction you can apply is a context bound. So if you have a list of classes that are okay, you create an implicit object HasStar[X] for each class X and use a context bound like T:HasStar. I know this probably isn't exactly what you want, but I don't think there are any better options.

Upvotes: 0

Related Questions