2

I have some overloaded methods that take in multiple types and return the same type:

def foo(x: Int): Foo = ... 
def foo(x: String): Foo = ... 
def foo(x: Boolean): Foo = ... 
def foo(x: Long): Foo = ...

Now I want to define a single way to call the method, something like:

def bar(x: Int | String | Boolean | Long) = foo(x)  // how to do this? 

I can do it the "naive" way which I don't like very much:

def bar(x: Any) = x match {
  case i:Int => foo(i)
  case s:String => foo(s)
  case b:Boolean => foo(b)
  case l:Long => foo(l)
  case _ => throw new Exception("Unsupported type")
}

Is there a better way, perhaps using Scalaz or some other library?

Jus12
  • 17,824
  • 28
  • 99
  • 157
  • 1
    [**typeclasses**](https://tpolecat.github.io/2013/10/12/typeclass.html) is the answer. Your naive approach has the problem that if fails at runtime instead of compile-time, and it is not very extensible due _type erasure_. Actually, you can use any kind of [polymorphism](https://gist.github.com/BalmungSan/c19557030181c0dc36533f3de7d7abf4) all from the beginning. – Luis Miguel Mejía Suárez May 06 '20 at 16:26
  • 2
    Scala 2 does not support *union types*, but Scala 3 will. – Jörg W Mittag May 06 '20 at 16:31
  • Sadly, even in Dotty (Scala 3), we need to do an explicit match on `x`, as shown here: https://scastie.scala-lang.org/n9gnZCSjRSm4Cj90TAduHg – Jus12 May 07 '20 at 15:08
  • https://stackoverflow.com/questions/3508077/how-to-define-type-disjunction-union-types – Dmytro Mitin Jun 07 '20 at 00:30

2 Answers2

4

Try type class

trait FooDoer[T] {
  def foo(x: T): Foo
}
object FooDoer {
  implicit val int: FooDoer[Int] = (x: Int) => foo(x)
  implicit val string: FooDoer[String] = (x: String) => foo(x)
  implicit val boolean: FooDoer[Boolean] = (x: Boolean) => foo(x)
  implicit val long: FooDoer[Long] = (x: Long) => foo(x)
}

def bar[T](x: T)(implicit fooDoer: FooDoer[T]): Foo = fooDoer.foo(x)

bar(1)
bar("a")
bar(true)
bar(1L)
// bar(1.0) // doesn't compile

Also sometimes the following can help

def bar[T](x: T)(implicit ev: (T =:= Int) | (T =:= String) | (T =:= Boolean) | (T =:= Long)) = ???

trait |[A, B]
trait LowPriority_| {
  implicit def a[A, B](implicit a: A): A | B = null
}
object | extends LowPriority_| {
  implicit def b[A, B](implicit b: B): A | B = null
}

How to define "type disjunction" (union types)?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Are you sure this compiles? How do those methods get turned into instances of `FooDoer`? – Tim May 06 '20 at 16:44
  • 2
    With latest Scala (2.13) it compiles https://scastie.scala-lang.org/BAxSwLCiTNGU6C82JeqGTA – Jus12 May 06 '20 at 16:48
  • 3
    @Tim this works starting from Scala 2.12 https://www.scala-lang.org/news/2.12.0/#lambda-syntax-for-sam-types Read about SAM. – Dmytro Mitin May 06 '20 at 16:50
  • OK, I get it. Thanks. I'm not sure if this is cool or just confusing, but I learnt something today so that is a win! But I'm not sure I will ever use this in my own code. – Tim May 06 '20 at 17:09
1

A typeclass might work like this:

trait CanFoo[T] {
  def foo(t: T): Foo
}

object CanFoo {
  implicit object intFoo extends CanFoo[Int] {
    def foo(i: Int) = Foo(i)
  }
  implicit object stringFoo extends CanFoo[String] {
    def foo(s: String) = Foo(s)
  }
  implicit object boolFoo extends CanFoo[Boolean] {
    def foo(i: Boolean) = Foo(i)
  }
  implicit object longFoo extends CanFoo[Long] {
    def foo(i: Long) = Foo(i)
  }
}

def bar[T](x: T)(implicit ev: CanFoo[T]) =
  ev.foo(x)

bar(0)
bar("hello")
bar(true)
bar(0.toLong)
Tim
  • 26,753
  • 2
  • 16
  • 29
  • This also compiles, with slight change in variable names. https://scastie.scala-lang.org/vwJaUWcCRk28mVdWWwyb1Q – Jus12 May 06 '20 at 16:52
  • 1
    @Jus12 Yes, I was just short-circuiting the `foo` methods and creating the `Foo` directly. The other answer is better :) – Tim May 06 '20 at 17:07