tags:

views:

355

answers:

7

One way that has been suggested to deal with double definitions of overloaded methods is to replace overloading with pattern matching:

object Bar {
   def foo(xs: Any*) = xs foreach { 
      case _:String => println("str")
      case _:Int => println("int")
      case _ => throw new UglyRuntimeException()
   }
}

This approach requires that we surrender static type checking on the arguments to foo. It would be much nicer to be able to write

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case _: String => println("str")
      case _: Int => println("int")
   }
}

I can get close with Either, but it gets ugly fast with more than two types:

type or[L,R] = Either[L,R]

implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case Left(l) => println("str")
      case Right(r) => println("int")
   }
}

It looks like a general (elegant, efficient) solution would require defining Either3, Either4, .... Does anyone know of an alternate solution to achieve the same end? To my knowledge, Scala does not have built-in "type disjunction". Also, are the implicit conversions defined above lurking in the standard library somewhere so that I can just import them?

+2  A: 

Well, that's all very clever, but I'm pretty sure you know already that the answers to your leading questions are various varieties of "No". Scala handles overloading differently and, it must be admitted, somewhat less elegantly than you describe. Some of that's due to Java interoperability, some of that is due to not wanting to hit edged cases of the type inferencing algorithm, and some of that's due to it simply not being Haskell.

Dave Griffith
While I've been using Scala for a while, I'm neither as knowledgeable nor as smart as you seem to think. In this example, I can see how a library could provide the solution. It makes sense to then wonder whether such a library exists (or some alternative).
Aaron Novstrup
+3  A: 

You might take a look at MetaScala, which has something called OneOf. I get the impression that this doesn't work well with match statements but that you can simulate matching using higher-order functions. Take a look at this snippet, for instance, but note that the "simulated matching" part is commented out, maybe because it doesn't quite work yet.

Now for some editorializing: I don't think there's anything egregious about defining Either3, Either4, etc. as you describe. This is essentially dual to the standard 22 tuple types built in to Scala. It'd certainly be nice if Scala had built-in disjunctive types, and perhaps some nice syntax for them like {x, y, z}.

pelotom
+2  A: 

If you can use scala 2.8, take a look into type classes. It does something very similar to what you are asking, but you will jave to create implicit functions. Ghosh gives an example of a type safe json serializer.

Daniel Ribeiro
+12  A: 

Well, in the specific case of Any*, this trick below won't work, as it will not accept mixed types. However, since mixed types wouldn't work with overloading either, this may be what you want.

First, declare a class with the types you wish to accept as below:

class StringOrInt[T]
object StringOrInt {
  implicit object IntWitness extends StringOrInt[Int]
  implicit object StringWitness extends StringOrInt[String]
}

Next, declare foo like this:

object Bar {
  def foo[T: StringOrInt](x: T) = x match {
    case _: String => println("str")
    case _: Int => println("int")
  }
}

And that's it. You can call foo(5) or foo("abc"), and it will work, but try foo(true) and it will fail. This could be side-stepped by the client code by creating a StringOrInt[Boolean], unless, as noted by Randall below, you make StringOrInt a sealed class.

It works because T: StringOrInt means there's an implicit parameter of type StringOrInt[T], and because Scala looks inside companion objects of a type to see if there are implicits there to make code asking for that type work.

Daniel
If `class StringOrInt[T]` is made `sealed`, the "leak" you referred to ("Of course, this could be side-stepped by the client code by creating a `StringOrInt[Boolean]`") is plugged, at least if `StringOrInt` resides in a file of its own. Then the witness objects must be defined in the same souce as `StringOrInt`.
Randall Schulz
I tried generalizing this solution somewhat (posted as an answer below). The main drawback compared to the `Either` approach seems to be that we lose a lot of compiler support for checking the match.
Aaron Novstrup
+3  A: 

There is also this <em>hack</em>:

implicit val x: Int = 0
def foo(a: List[Int])(implicit ignore: Int) { }

implicit val y = ""
def foo(a: List[String])(implicit ignore: String) { }

foo(1::2::Nil)
foo("a"::"b"::Nil)

See http://michid.wordpress.com/2010/06/14/working-around-type-erasure-ambiguities-scala/

michid
See http://stackoverflow.com/questions/3422336/how-can-i-differentiate-between-def-fooaxs-a-and-def-fooa-bxs-a-b/3424314#3424314. There's actually an easier hack: just add `(implicit e: DummyImplicit)` to one of the type signatures.
Aaron Novstrup
+2  A: 

A type class solution is probably the nicest way to go here, using implicits. This is similar to the monoid approach mentioned in the Odersky/Spoon/Venners book:

abstract class NameOf[T] {
  def get : String
}

implicit object NameOfStr extends NameOf[String] {
  def get = "str"
}

implicit object NameOfInt extends NameOf[Int] {
 def get = "int"
}

def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get)

If you then run this in the REPL:

scala> printNameOf(1)
int

scala> printNameOf("sss")
str

scala> printNameOf(2.0f)
<console>:10: error: could not find implicit value for parameter nameOf: NameOf[
Float]
       printNameOf(2.0f)

              ^
Kevin Wright
I could be wrong, but I don't think this is what the OP was looking for. OP was asking about a data type which could represent a disjoint union of types, and then do case analysis on it *at runtime* to see what the actual type turned out to be. Type classes won't solve this problem, as they're a purely compile-time construct.
pelotom
The *real* question being asked was how to expose different behaviour for different types, but without overloading. Without knowledge of type classes (and perhaps some exposure to C/C++), a union type appears to be the only solution. Scala's pre-existing `Either` type tends to reinforce this belief. Using type classes via Scala's implicits is a better solution to the underlying problem, but it's a relatively new concept and still not widely known, which is why the OP didn't even know to consider them as a possible alternative to a union type.
Kevin Wright
+2  A: 

It's possible to generalize Daniel's solution as follows:

sealed trait Or[A, B]

object Or {
   implicit def a2Or[A,B](a: A) = new Or[A, B] {}
   implicit def b2Or[A,B](b: B) = new Or[A, B] {}
}

object Bar {
   def foo[T <% String Or Int](x: T) = x match {
     case _: String => println("str")
     case _: Int => println("int")
   }
}

The main drawbacks of this approach are

  • As Daniel pointed out, it does not handle collections/varargs with mixed types
  • The compiler does not issue a warning if the match is not exhaustive
  • The compiler does not issue an error if the match includes an impossible case
  • Like the Either approach, further generalization would require defining analogous Or3, Or4, etc. traits. Of course, defining such traits would be much simpler than defining the corresponding Either classes.

Update:

Mitch Blevins demonstrates a very similar approach and shows how to generalize it to more than two type, dubbing it the "stuttering or".

Aaron Novstrup