Alexey Alexandrov
Alexey Alexandrov

Reputation: 3129

Emulating function overload in Scala

I am rather new to Scala programming, so still figuring out canonical ways to doing something. Lately, I wanted to write a function foo that would only accept a set of types for its only parameter. Think of something like

def foo(x: [Int, String]) = ???

But I couldn't find anything close to the syntax above (which I find most natural to me). Using type Any would loose the compile-side checking and would make it easier for problems to escape into the runtime land.

The best I was able to come up with is something like this:

sealed abstract class Base
case class TypeInt(v: Int) extends Base
case class TypeString(v: String) extends Base

implicit def toTypeInt(v: Int) = TypeInt(v)
implicit def toTypeString(v: String) = TypeString(v)

def foo(x: Base) = x match {
  case TypeInt(v) => println("Int: ", v)
  case TypeString(v) => println("String: ", v)
}

foo(1)
foo("hello")

(As a side note, I would like to be able to just write implicit case class ... to avoid creating manually the toType* functions but that doesn't compile.)

Is there a simpler way to write a function that accepts a parameter of a set of types in a typesafe manner?

UPDATE: It actually turns out that in my specific case I could just have used method overloading. For some reason it's not possible to use method overloading at all in Scala worksheet which made me think that Scala doesn't have overloading at all. But I was wrong - in regular Scala source it should be possible to use that. There are still some shortcomings of overload usage that are described in the article on Magnet pattern that was mentioned in comments below (e.g. there is no way to overload on type Foo[Type1] and Foo[Type2] because of type erasure in JVM generics.

Upvotes: 2

Views: 205

Answers (1)

Travis Brown
Travis Brown

Reputation: 139038

The magnet pattern feels like overkill here—you can use plain old type classes:

trait Fooable[A] { def apply(a: A): Unit }

implicit object intFooable extends Fooable[Int] {
  def apply(a: Int) = printf("Int: %d\n", a)
}

implicit object stringFooable extends Fooable[String] {
  def apply(a: String) = printf("String: %s\n", a)
}

def foo[A: Fooable](a: A) = implicitly[Fooable[A]].apply(a)

And then:

scala> foo(1)
Int: 1

scala> foo("hello")
String: hello

Suppose you're worried about collisions after erasure. Let's try some instances for a generic type:

trait Bar[A]

implicit object barIntFooable extends Fooable[Bar[Int]] {
  def apply(a: Bar[Int]) = println("A bar of ints.")
}

implicit object barStringFooable extends Fooable[Bar[String]] {
  def apply(a: Bar[String]) = println("A bar of strings.")
}

And again:

scala> foo(new Bar[Int] {})
A bar of ints.

scala> foo(new Bar[String] {})
A bar of strings.

Everything works as expected.

Upvotes: 4

Related Questions