0

I'm writing a Scala library to operate upon Spark DataFrames. I have a bunch of classes, each of which contain a function that operates upon the supplied DataFrame:

class Foo(){val func = SomeFunction(,,,)}
class Bar(){val func = SomeFunction(,,,)}
class Baz(){val func = SomeFunction(,,,)}

The user of my library passes a parameter operation: String to indicate class to instantiate, the value passed has to be the name of one of those classes hence I have code that looks something like this:

operation match {
  case Foo => new Foo().SomeFunction
  case Bar => new Bar().SomeFunction
  case Baz => new Baz().SomeFunction
}

I'm a novice Scala developer but this seems rather like a clunky way of achieving this. I'm hoping there is a simpler way to instantiate the desired class based on the value of operation given that it will be the same as the name of the desired class.

The reason I want to do this is that I want external contributors to contribute their own classes and I want to make it at easy as possible for them to do that, I don't want them to have to know they also need to go and change a pattern match.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
jamiet
  • 10,501
  • 14
  • 80
  • 159
  • Not sure what you find clunky about pattern matching :D It is easy to understand and clean. There isn't really much you can do here. You need a control flow that tells the program what to make of the String, since as is they don't represent any type information. I don't know exactly you are trying to do, but I am gonna go ahead and say having all those classes seem to be thing that is off here. Why do you need classes? What do they give you Objects or Type Classes wouldn't? – sinanspd May 20 '20 at 20:16
  • 3
    You COULD technically make this work with reflection. See : https://stackoverflow.com/questions/32400209/reflect-type-name-in-string-to-actual-type-in-scala but I guarantee you, reflection + spark, you will end up quitting programming all together. It would definitely be overkill. (Intentional exaggeration in the comments to support the argument that pattern matching is elegant) – sinanspd May 20 '20 at 20:17
  • I accept pattern matching is an elegant technique, my use of the word "clunky" was incorrect. I have edited the question to elaborate why I want to do this. – jamiet May 20 '20 at 20:20
  • I'm not yet experienced/knowledgeable enough to answer "What do they give you Objects or Type Classes wouldn't? " – jamiet May 20 '20 at 20:21
  • 2
    If you know your type in compile-time you can use type classes to provide basic functionality and let users extend it. If you have some `trait X` and users provide extensions by implementing it, so we might not know it at runtime, you can use abstract methods. – Mateusz Kubuszok May 20 '20 at 21:00
  • For String input I really can't try to find a better solution of the pattern matching. But, if you can change the input type with some object you can do something like that with the Type class pattern – gccodec May 20 '20 at 21:06

1 Answers1

1

For

case class SomeFunction(s: String)

class Foo(){val func = SomeFunction("Foo#func")}
class Bar(){val func = SomeFunction("Bar#func")}
class Baz(){val func = SomeFunction("Baz#func")}
//...

reflection-based version of

def foo(operation: String) = operation match {
  case "Foo" => new Foo().func
  case "Bar" => new Bar().func
  case "Baz" => new Baz().func
  // ...
}

is

import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._

def foo(operation: String): SomeFunction = {
  val runtimeMirror = universe.runtimeMirror(getClass.getClassLoader)
  val classSymbol = runtimeMirror.staticClass(operation)
  val constructorSymbol = classSymbol.primaryConstructor.asMethod
  val classMirror = runtimeMirror.reflectClass(classSymbol)
  val classType = classSymbol.toType
  val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
  val instance = constructorMirror()
  val fieldSymbol = classType.decl(TermName("func")).asTerm
  val instanceMirror = runtimeMirror.reflect(instance)
  val fieldMirror = instanceMirror.reflectField(fieldSymbol)
  fieldMirror.get.asInstanceOf[SomeFunction]
}

Testing:

foo("Foo") //SomeFunction(Foo#func)
foo("Bar") //SomeFunction(Bar#func)
foo("Baz") //SomeFunction(Baz#func)
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66