Parametrize a method for type which implements certain methods

I’ve read lots of Scala books in the past year, and I forgot what I read where.
I seem to recall there was a way in Scala to specify that a type parameter may represent any type which implements a certain method(s). Did I dream that or is it the case?

for example, I’d like A to be any type for which + is defined.

    def sumTuple[A](pair: (A, A)): A = {
      val (m1, m2) = pair
      m1 + m2
    }

The term you want to search for is “structural types”

1 Like

I gave it a try, and I seem to have some compilation errors. Is this telling me that Double does not have a + Double method?

object FoldTrig {
  def sumTuple[A <: {def +(a : A) : A}](pair: (A, A)): A = {
    val (m1, m2) = pair
    m1 + m2
  }

  val nTerms = 10

  //           1    x^1   x^2   x^3
  // exp(x) = --- + --- + --- + --- + ...
  //           0!    1!    2!    3!
  def exp(x: Double): Double = {
    sumTuple((1 until nTerms).foldLeft((0.0, 1.0)) {
      case ((sum, term), n) => (sum + term, (term * x) * (1.0 / n))
    })
  }
}
Error:(15, 5) inferred type arguments [Double] do not conform to method sumTuple's type parameter bounds [A <: AnyRef{def +(a: A): A}]
    sumTuple((1 until nTerms).foldLeft((0.0, 1.0)) {

type parameter bounds [A <: AnyRef{def +(a: A): A}]
Seems that structural types only work with reference (AnyRef) types. Double is a value (AnyVal) type.

What you could try is something like:

import scala.math.Numeric.Implicits._

def sumTuple[A: Numeric](pair: (A, A)): A = {
  val (m1, m2) = pair
  m1 + m2
}
1 Like

I’m not sure what Numeric is, but I’m pretty sure my types I’m created with class definitions are not Numeric, I’m basically create a class to manage immutable square matrices, and defining sin, cos, exp etc on them using the formulas for the Taylor series.

I have methods like the following. The method sumTuple is my vain attempt to try to test the code with numbers. When debugging, I was wondering whether the formula was correct. I.e., had I translated the Taylor series correctly into the foldLeft Checking it with a number was my first attempt. So I finally just checked it with a diagonal matrix, which worked.

In the end duplicating the definition of sumTuple to work with Double isn’t much work. But it would be nice if I didn’t have to write redundant code.


    def sumTuple(pair: (sqMatrix, sqMatrix)): sqMatrix = {
      val (m1, m2) = pair
      m1 + m2
    }
    //           1    x^1   x^2   x^3
    // exp(x) = --- + --- + --- + --- + ...
    //           0!    1!    2!    3!
    def exp(nterms: Int): sqMatrix = sumTuple((1 until nterms).foldLeft((zero, identity)) {
      case ((sum, term), n) => (sum + term, (term * this) * (1.0 / n))
    })

BTW, is there something about structural types which inherently don’t work with AnyVal? What is AnyRefy about them?

Numeric is a typeclass, so I suspect you could make this work if you wanted to. Essentially, Numeric is an abstract interface describing a bunch of numeric operations; you then write an adapter object (a “typeclass instance”) saying how to implement that interface for your particular numeric type.

Typically you would put this into your companion object, something like this (details obviously TBD, and this is off the top of my head, so please forgive errors):

object Matrix { // assuming your type is named Matrix
  implicit object matrixNumeric extends Numeric[Matrix] {
    // The usual -1, 0, 1 result for "less than", "equals", "greater than"
    def compare(x: Matrix, y: Matrix): Int = ...

    // Just stub out functions that make no sense
    def fromInt(i: Int): Matrix = ???

    // Implement the functions that do make sense
    def plus(x: Matrix, y: Matrix): Matrix = ...

    // etc -- need to implement at least stubs for all of the methods
  }
}

In and of itself, that doesn’t provide +, but there is an adapter that transforms the plus() function to the usual + operator. Your code would then take that typeclass and use it as @tarsa showed.

If that’s too much hassle for the matrix type, you could define a stripped-down version of Numeric yourself, that only includes the functions you need – adding a new typeclass is pretty easy.

Once you grok typeclasses, they’re a really useful and versatile tool, exactly right for when you have disparate classes for which you want to use the same interface. They are far, far more common in idiomatic Scala code than structural types are.

Don’t know for sure, but I assume it has to do with the fact that structural types are based on JVM reflection. This means that they are relatively inefficient, don’t work on other target platforms, and have some odd limitations like this. In practice, I rarely see them used, and they are being replaced by a more general mechanism in Scala 3.

2 Likes

This doesn’t compile for me on 2.12.8: “Parameter type in structural refinement may not refer to an abstract type defined outside that refinement”, both in sbt (1.2.8) and IntelliJ. Does anybody have an idea what the problem/difference could be…?

I really like the concept of Numeric. I’m putting it high on my list of cool Scala to learn. I really like the idea that I can kick the can down the road with the ??? trick, and omit implementing methods I don’t care about yet, or don’t care about for this particular application.

I’m getting the same problem on ScalaFiddle and Scastie. Maybe @jimka ran into some miscompilation issue?

??? is not really a trick. It’s just a weirdly named method residing in scala.Predef and throwing an specific Error. You can define it yourself:

// this is considered a fatal exception
def ??? : Nothing = throw new NotImplementedError("not implemented yet")

// this is considered NonFatal, so it wouldn't break ScalaTest spec
def $$$ : Nothing = throw new Exception("not implemented yet")
1 Like

@tarsa, I think you underestimate the coolness of ???.

1 Like

Oh, it’s definitely cool, and a great teaching tool - this and exceptions are the best illustrations of what ‘Nothing’ means, and why it’s useful…

2 Likes