Reputation: 13
I need some help trying to figure out how to reuse a pattern match that I would rather not repeat (if possible). I have searched here and google, experimented with implicits and variance but to no result so far.
In the below are 2 methods, doSomething and doSomethingElse that contain the same pattern match on Ids. I would like to reuse the pattern by passing in a function.
This the initial setup. (The actual implementations of toPath and take2 are not really relevant.)
import java.nio.file.{Paths, Path}
import java.util.UUID
def take2(x: Long): String = {
(x % 100).toString.padTo(2, '0')
}
def take2(u: UUID): String = {
u.toString.take(2)
}
def toPath(x: Long): Path = {
Paths.get(s"$x")
}
def toPath(u: UUID): Path = {
Paths.get(u.toString)
}
case class Ids(id1: Option[Long], id2: Option[UUID])
def doSomething(ids: Ids): String = ids match {
case Ids(_, Some(uuid)) => take2(uuid)
case Ids(Some(long), _) => take2(long)
}
def doSomethingElse(ids: Ids) = ids match {
case Ids(_, Some(uuid)) => toPath(uuid)
case Ids(Some(long), _) => toPath(long)
}
doSomething(Ids(Some(12345L), None))
doSomethingElse(Ids(Some(12345L), None))
What I would like is for something like this to work:
def execute[A](ids: Ids)(f: Any => A): A = ids match {
case Ids(_, Some(uuid)) => f(uuid)
case Ids(Some(long), _) => f(long)
}
def _doSomething(ids: Ids) = execute[String](ids)(take2)
//def _doSomething2(ids: Ids) = execute[Path](ids)(toPath)
The error I get is:
Error: ... type mismatch;
found : (u: java.util.UUID)String <and> (x: Long)String
required: Any => String
def _doSomething(ids: Ids) = execute[String](ids)(take2)
^ ^
How can I make these function types work please?
My Scala version 2.11.2.
Worksheet I have been using: https://github.com/lhohan/scala-pg/blob/0f1416a6c1d3e26d248c0ef2de404bab76ac4e57/src/main/scala/misc/MethodPassing.sc
Any help or pointers are kindly appreciated.
Upvotes: 0
Views: 610
Reputation: 167871
The problem is that you have two different methods that just happen to share the same name, e.g. "take2". When you try to use take2
you certainly aren't providing a function that can handle any argument type (as Any => A
demands); you can't even handle the two types you want since they are two different methods!
In your original match statement you don't notice that the two methods are two methods that share the same name because the compiler fills in the correct method based on the argument type. There isn't a feature that says, "plug in the name I supply and then stick in different methods". (Well, you could do it with macros, but that's awfully complicated to avoid a little bit of repetition.)
Now the compiler is smart enough to make a function out of the method you want. So if you wrote
def execute[A](ids: Ids)(f1: UUID => A, f2: Long => A): A = ids match {
case Ids(_, Some(uuid)) => f1(uuid)
case Ids(Some(long), _) => f2(long)
}
you could then
def doSomething(ids: Ids) = execute[String](ids)(take2, take2)
which would reduce the repetition a bit.
Alternatively, you could write
import scala.util._
def take2(x: Long): String = (x % 100).toString.padTo(2, '0')
def take2(u: UUID): String = u.toString.take(2)
def take2(ul: Either[UUID, Long]): String = ul match {
case Left(u) => take2(u)
case Right(l) => take2(l)
}
(be sure to use :paste
if you try this out in the REPL so all three get defined together).
Then you write
def execute[A](ids: Ids)(f: Either[UUID, Long] => A): A = ids match {
case Ids(_, Some(uuid)) => f(Left(uuid))
case Ids(Some(long), _) => f(Right(long))
}
and the correct one of the three take2
s will be used. (There is a runtime penalty associated with the extra boxing of the arguments, but I doubt that this is a performance-critical code path.)
There are other options as well--Shapeless, for instance, provides union types. Or you can do runtime pattern matching and throw an exception if you pass something that is neither UUID
nor Long
...but that is liable to be a recipe for trouble later.
Upvotes: 1