0
votes

Is it possible to constrain a Scala generic type to only types that have overloaded a specific operator?

Example:

def foo[T](...):T = {...}
foo(...) * .70

Searching Google has yet to produce a solution. I considered restricting T to be of type Numeric (or scala's equivalent), but then I want to allow users to define custom types and specify their own behaviors for these operators.

2

2 Answers

4
votes

Use a typeclass.

trait Multiply[A] {
  def multiply(x: A, y: Double): A
}

object Multiply {
  def apply[A](implicit instance: Multiple[A]): Multiply[A] = instance

  trait Ops[A] {
    def typeClassInstance: Multiply[A]
    def self: A
    def *(y: Double): A = typeClassInstance.multiply(self, y)
  }

  trait ToMultiplyOps {
    implicit def toMultiplyOps[A](target: A)(implicit tc: Multiply[A]): Ops[A] = new Ops[A] {
      val self = target
      val typeClassInstance = tc
    }
  }

  trait AllOps[A] extends Ops[A] {
    def typeClassInstance: Multiply[A]
  }

  object ops {
    implicit def toAllMultiplyOps[A](target: A)(implicit tc: Multiply[A]): AllOps[A] = new AllOps[A] {
      val self = target
      val typeClassInstance = tc
    }
  }
}

(Note that this is equivalent to what's generated by Michael Pilquist's simulacrum library, so you don't really need all this boilerplate)

Then your users can do this:

case class Foo(x: Int)

object Foo {
  implicit val multiplyFoo: Multiply[Foo] = new Multiply[Foo] {
    def multiply(foo: Foo, y: Double): Foo = ???
  }
}

Foo(42) * 3.1415
2
votes

Yes, there are so-called structural types. You can define them as follows:

def foo[T <: {def yourOperator:Unit}(...) : T = {...}

where yourOperator is the name of the method definition and Unit the return type. In this example T will accept every type which is declaring a method called yourOperator with Unit as return type.

This could be used for example to handle a resource which is Closeable. Imagine the following code.

def operatorOnResource[T <: {def close : Unit}](resource: T) : Unit = {
    //do stuff with the resource
    resource.close
}

Please note that this example is also kind of a risk, because T has only constraints on its structure. Hence, even if T satisfies the structural needs the semantic of the close could be different.