1

I want to write a generic function functionChooser which will choose which function to use from a few options, based on a String argument.

This works:

def a (arg: String) = arg + " with a"
def b (arg: String) = arg + " with b"
def c (arg: String) = arg + " with c"

def functionChooser(func: String, additionalArg: String) = {
    val f = func match {
      case "a" => a _
      case "b" => b _
      case _ => c _
    }

    f(additionalArg)
}

scala> functionChooser("a", "foo")
res18: String = foo with a

I'm having trouble in making functionChooser generic, e.g. when functions a, b, and c return different case classes:

case class A(s: String)
case class B(s: String)
case class C(s: String)

def a (arg: String) = A(arg)
def b (arg: String) = B(arg)
def c (arg: String) = C(arg)

//functionChooser def as before

scala> functionChooser("a", "foo")
res19: Product with Serializable = A(foo)

I don't quite understand what I got there, I know I get an error when calling functionChooser("a", "foo").s ("error: value s is not a member of Product with Serializable").

Lastly, what I really want is that the functions would return Lists of these case classes, e.g.:

def a (arg: String) = List(A(arg))
def b (arg: String) = List(B(arg))
def c (arg: String) = List(C(arg))

So functionChooser should be generic to List[T] where T is some class.

2 Answers 2

4

The function functionChooser will return the most specific common super type of the case classes A, B and C. Since case classes inherit from Product and Serializable, the common super type is Product with Serializable.

If you want to access the case class field s you either have to cast the result, via pattern matching, or you provide a common base class of all your classes A, B and C which allows you to access the field.

trait Base {
  def s: String
}

case class A(s: String) extends Base
case class B(s: String) extends Base
case class C(s: String) extends Base

With this type definition the return type of functionChooser would be Product with Serializable with Base and, thus, the result would allow you to access s.

If your function a, b and c return a List of the respective case class, then the return type of functionChooser would be List[Product with Serializable with Base].

Update

If you cannot change the class hierarchy, then you either have to cast the result or you could extract the necessary information in the functionChooser and wrap it in another type which contains the super set of all data you need. E.g.

def functionChooser(func: String, additionalArg: String): String = {
    val f = func match {
      case "a" => (a _).s
      case "b" => (b _).s
      case _ => (c _).s
    }

    f(additionalArg)
}

Note: Here I only extract the field s which is the super set of all required information.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you @Till, what if that is not possible, e.g. I'm using external Classes, cannot implement them?
@GioraSimchoni, then you either have to cast the result or wrap the required information in another type. I've updated my answer accordingly.
I will try playing a bit with casting. Regarding s member this is just a simple example, I'm talking bout much more complex classes, which do not share the same members. f is also more complex, but it is common to all options. Thanks again.
@GioraSimchoni, what is the point of having a single functionChooser that can deal with different input types? I guess it will extract something common from each of them, right? If that's the case, you can achieve the same goal with type classes.
@IhorKaharlichenko the f function is used inside some logic of a few lines of code, not written here for simplicity. if (condition1) f(1) else if (condition2) f(2) else f(3). And the same logic applies to functions a, b and c (and more), which deal with external classes I cannot refactor and are themselves external and cannot be refactored. I assumed that still, all the if else logic need not be written again and again, but have a generic g function or functionChooser for it.
1

You should return the upper common type for all three functions. Object (AnyRef) always fit.

def functionChooser(func: String, additionalArg: String) : AnyRef = {

In your case, where all possible returning values are Lists you may use more specific type:

def functionChooser(func: String, additionalArg: String) : List[_] = {

Of course that will eliminate type information. Any method should return the same type, it could not be polymorphic on it. So, you need to use .asInstanceOf[T] case further, to get this information back.

That make sense, because the actual type is unknown during runtime. e.g. the dispatcher string may be entered by user. If it would be known during compile time, then you could just use appropriate method without referring to descriptive string.

If you would like to get some common behaviour for all possible return types, than you should define a common trait for them and place common methods to it.

3 Comments

Doesn't do what I want, for functionChooser("a", "foo").s I would still get error: value s is not a member of AnyRef.
It is inevitable, the actual result type is unknown in compiler time
Fantastic, your .asInstanceOf edit did the work. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.