Function overloading for user defined class as parameter

Consider the following code:

case class UserDefined[T](data: T){
 var z = data
}

case class somethingelse(){
 def func1(data: UserDefined[Int]) = {

 }
 def func1(data: UserDefined[Double]) = {

 }
 // The above 2 functions give error: func1 is already defined. But the below one works
 def func1(data: Array[Int]) = {

 }
 def func1(data: Array[Double]) = {

 }
}

Overloading the last 2 functions works in scala. But the first 2 functions(func1(data: UserDefined[Int]), func1(data: UserDefined[Double])) gives an error.
I am using scala 2.13.1

Can anyone help me with this. :slight_smile:
Thank you!

Hi.

Please, if you ask a question stating that you get a compiler error, also say what the error is. That makes it much easier to help. Thanks!

Here, the problem is two fold (at least the explanation). First, Array is something special because of the JVM internals. An Int Array is represented differently from a Double Array (at the byte level).

But, normally, parameterized types undergo type erasure, so at runtime (at the byte level) they look all the same. So your code becomes:

def func1(data: UserDefined[_]) = {

}

def func1(data: UserDefined[_]) = {

}

which means that these two functions have exactly the same signature, which is not possible.

Either do not use overloading and you are safe. Otherwise, you could also parameterize the function itself:

def func1[T](data: UserDefined[T]) = { ... }
3 Likes

It would be good to mention that a common workaround for this is typeclasses, in this case I bet Numeric would be very helpful.

1 Like

Oh sure, I will keep that in mind.
Thank you for such a detailed explanation!

Could you please explain a bit more on this. I didn’t understand this work around.

Thank you!

Could you please explain a bit more on this. I didn’t understand this work around.

Sure, and sorry for taking so long, I was a bit busy (that is why I replied with just a suggestion).

So, typeclasses are a pattern for achieving Polymophism, which is more common in functional languages.

So how are those useful for cases when type erasure is a problem?
Because instead of having overloads you have just one method that works for any type that provides an instance of the typeclass.

Particularly in this case, since you want both Int and Double you probably only need the built-in Numeric typeclass in the stdlib.

2 Likes

Woho, that seems a great read! I 'll try out these links then!

Actually my use case is different than the one I put up, just to make it easy for explaining. :smile:
For my use-case: I require either of: Array[Array[Double]] or Array[Array[Double]] => Array[Array[Double]]

Anyways, thanks a lot for all the answers!

Nice writeup!

One more nuance that is worth calling out in this particular context: typeclasses work around this problem because typeclass polymorphism is figured at at compile time, when the compiler has lots of information. That way, you don’t get slammed by the problem of runtime type erasure.

(This is generally true: when you find that you can’t do something because of type erasure, switching to a typeclass-based model is the best solution 90% of the time.)

2 Likes

I require either of: Array[Array[Double]] or Array[Array[Double]] => Array[Array[Double]]

I am pretty sure the compiler is able to differentiate those two types, maybe a typo?
Anyways, you may define your own typeclass providing the functionality you want to expose for both types, just think of it as an interface (but more flexible).

BTW, are you sure you want to use plain Arrays? Those are quite rare in Scala.
If you do not need fast access by index, the common default collection is List. If you do need fast access by index you can use ArraySeq (new in 2.13) or Vector.

1 Like

So I am using intellij for writing all my code, and it shows an overloading error before the compilation itself.

Once again, I will read about typeclasses and try to implement it.

Thank you for this information! I don’t actually need fast access by index. Hence I 'll try out ArraySeq or Vector!

Thank you for the help!

Then show us the relevant code and the error you get. As @BalmungSan said, the compiler should have no problem distinguishing between an Array argument and a Function argument…

2 Likes

Hence I think the compiler is still confused in distinguishing an Array argument and a Function argument.

Does the above screenshot help? Or do you need something else along with this?

Here is the error when I try to compile this:

Oh, that is the same problem we’re started with. Since you have arguments of type UserDefined[ Array[Array[Double]] ] and UserDefined[ Array[Array[Double]] => Array[Array[Double]] ], that both boils down to UserDefined[_] because of type erasure again.

2 Likes

So, this is not the case… But, for future problems, if Intellij shows and error do not believe it and run a real compiler (probably using your build tool like SBT or Maven).
Intellij uses its own custom implementation of the typechecker which sometime may produce false positives.

Thank you for this information! I don’t actually need fast access by index. Hence I 'll try out ArraySeq or Vector!

Just to clarify, you meant yo actually need fast access by index? Because I said that if you don’t List would probably be a more common one. Anyways, as long as you use a real collection an not plain Arrays we are good.


How does those UserDefined are intended to work?
Because it is quite weird that you let the user chose any type T but your somethingelse only accepts a matrix and a function from a matrix to a matrix.
Because maybe another solution would be a simple ADT.

Sure, will keep this in mind.

Alright got it!

I actually removed some part of the code, just to simplify it. Here is how the code should actually look:

I am curious as to what is an ADT? :smiley:

So you already have a Typeclass.

Was that MatirxOrFunction added after our conversation? or it has been there since the beginning?


An ADT (algebraic data type) is just a fancy name for a sealed hierarchy of types.

Examples:

Option

An Option is Some with a value or a None

sealed trait Option[+T]

final case class Some[+T](value: T) extends Option[T]
final case object None extends Option[Nothing]

List

A List is an empty list or a non-empty list (called cons) with a head (a value) and a tail (a list).

sealed trait List[+T]

final case class Cons[+T](head: T, tail: List[T]) extends List[T]
final case object Nil extends List[Nothing]

etc

UserDefined?

Maybe you can model your UserDefined as an ADT instead.

sealed trait UserDefined[T] {
  def data: T
}

type Matrix = ArraySeq[ArraySeq[Double]
type MatrixFun = Matrix => Matrix

final case class MatrixUserDefined(data: Matrix) extends UserDefined[Matrix]
final case class MatrixFunUserDefined(data: MatrixFun) extends UserDefined[MatrixFun]

That way you can differentiate both types either as an overload or with pattern matching which would be the more common approach.

object Foo {
  private def processMatrix(matrix: Matrix): Unit = {
  }

  private def processMatrixFun(matrixFun: MatrixFun): Unit = {
  }

  def processUserDefined[T](data: UserDefined[T]): Unit = data match {
    case MatrixUserDefined(matrix) => processMatrix(matrix) // We statically know that matrix is of type Matrix.
    case MatrixFunUserDefined(matrixFun) => processMatrixFun(matrixFun) // We statically know that matrixFun is of type MatrixFun.
  }
}

PS: Not sure why you need that var z, but please reconsider its use.
Mutability is in general discouraged, especially inside a case class.

1 Like

It was there since the beginning. I just didn’t know that it is called Typeclass.

And OMG, this all code from you seems so interesting, but still difficult for me to grasp.

From what I understand from the code, I think according to this style of coding, I 'll need to specify the type in processUserDefined[T], right?
If yes, I am actually looking for something which has only processUserDefined, that is with no type specifying. Or I am guessing, I can remove this type specifier? and write it just as: processUserDefined(data: UserDefined[T])

Yup, I am not using it anywhere, I 'll remove that.

Yeah, actually I got confused while creating the snippet, sorry.
I have updated my previous post, can you check it out? I guess it should be clearer now, but if you still have questions do not doubt replying again.

1 Like

Amazing!
I tried it on scala REPL and it’s so close to exactly what I want!

Currently I have to pack an ArraySeq[ArraySeq[Double]] into MatrixUserDefined class and pass it to the processUserDefined function.
For instance:

val a = MatrixUserDefined(ArraySeq(ArraySeq(1, 1), ArraySeq(1, 1)))
processUserDefined(a)

Is it possible to directly provide an ArraySeq to processUserDefined function?
Something like:

processUserDefined(ArraySeq(ArraySeq(1, 1), ArraySeq(1, 1)))

Thanks a lot for your help!

Currently I have to pack an ArraySeq[ArraySeq[Double]] into MatrixUserDefined class and pass it to the processUserDefined function.

Well, that was the idea, wasn’t it?

I think we are starting to run in circles.
If you do not have a wrapper class simple overloading should work.
If it doesn’t due type eraruse you can overcome that either with a typelcass (if you have the type information statically) or an ADT (if you do not have the information statically).

So here it would be good to start over and think:

  • What is your goal?
  • What are your restrictions?
  • How do you expect your API to be used?
  • Will it be used for you (or your team) or by third parties?
  • Do you know the data at compile time or at runtime?
1 Like