From your question, it's not at all obvious why you would want to use dynamic invocation or reflection.
If all values A, ..., D belong to a common type Foo, then it probably won't get much easier than with case-classes:
sealed trait Foo
case class A(a1: Int, a2: String) extends Foo
case class B(b: Double) extends Foo
case class C(c: String) extends Foo
case object D extends Foo
Every time you want to transform A, ..., D to some other result type R, you can simply write a match-case, and handle the different cases there. If you use it so often that it is still too verbose, you can introduce an eliminator of the type Foo, which accepts bunch of functions, and hides the pattern matching inside:
object Foo {
def elim[R](
aElim: (Int, String) => R,
bElim: Double => R,
cElim: String => R,
dElim: R
)(f: Foo): R = f match {
case A(a1, a2) => aElim(a1, a2)
case B(b) => bElim(b)
case C(s) => cElim(s)
case D => dElim
}
}
In this way, you can trade the naming of the cases in match-case for a shorter syntax, e.g. you could construct an eliminator like this:
val fooToString = Foo.elim(_ + _, "" + _, identity, "D") _
or apply it to a list of As ... Ds directly:
List(A(42, "foo"), B(3.1415d), C("bar"), D).
map(Foo.elim(_ + _, "" + _, identity, "D")).
foreach(println)
This would output
42foo
3.1415
bar
D
If this isn't worth it, just use case classes with usual pattern matching.