hanlho
hanlho

Reputation: 13

Scala type mismatch while trying to pass a function

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

Answers (1)

Rex Kerr
Rex Kerr

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 take2s 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

Related Questions