Scala type bounds - scala

What is wrong with the code below? I'm getting the following complaint from the compiler on the indicated line: type arguments [Asset] do not conform to trait Worker's type parameter bounds [T <:
br.Doable]
How is this so? Worker expects a subtype of Doable, and asset extends Doable.
trait Doable
trait Worker[T<:Doable] {
def hey():String
}
case class Asset() extends Doable
case class Hey[Asset] extends Worker[Asset] { // << error here
def hey() = "You!"
}

When you declare case class Hey[Asset], you bind a new type variable Asset, you do not refer to case class Asset() extends Doable (you are shadowing the Asset type variable).
Your code is equivalent to :
case class Hey[A] extends Worker[A] {
...
}
which obviously won't work.

The problem is you have confused yourself by using the same value, Asset, to refer to a case class and a type parameter.
You probably intend to do something like this:
case class Hey[T](str: String) extends Worker[Asset] {
def hey() = "You!"
}
Though it is beside the point, note that I added a parameter to Hey because case classes without parameters have been deprecated.

This has been asked a lot of times, and I think that the confusion can easily go away if you see the analogy between type parameters and constructor parameters, thinking about them just as different kinds of constructor parameters: type-level and value-level.
Disclaimer Of course, this is only an analogy and it will break at a lot of different levels and there are a lot of corner cases as with anything Scala; but my point here is that it can be useful
At the type level, you can think of <: as a the equivalent of : at the value level:
class TypeParamsVsVals {
type X
type X1 <: X
class Buh[T <: X]
class Oh[T1 <: X1] extends Buh[T1]
// wait for Scala 3
// class Oh[T1 <: X1] extends Buh[T = T1]
type x
type x1 <: x
class buh(val t: x)
class oh(val t1: x1) extends buh(t = t1)
}
What I think is the main source of confusion is that at the type level there's no kind distinction between the two sides of <:, and to make things worse you can write T without any (no pun intended) bound, while you cannot do the same at the value level:
class NoBounds[T]
// same as
class AltNoBounds[T <: Any]
// you cannot write
// class noBounds(val t)
class noBounds(val t: Any)

Related

Achieving covariant return types on type level in presence of a non-finitary class graph

Imagine a pretty ordinary piece of code:
trait Template[X, +T] {
def m :T
}
trait Root[X] extends Template[X, Wrap[X]] {
override def m = new Wrap[X]
}
trait Sub[X] extends Root[X] with Template[X, Wrap2[X]] {
override def m = new Wrap2[X]
}
trait Wrap[X] extends Root[Option[X]]
trait Wrap2[X] extends Sub[X]
Root is a base class of a class hierarchy which happens to have method m the return type of which is occasionally narrowed by subclasses, as per Sub. An additional requirement is that I need that return type encoded somehow in the type of any C <: Root[_], so I can generalize over it. This is done through trait Template: I can in my implicits declare type parameters [X, T, W <: Template[X, T]] (forget for now problems with type inference here).
The problem is, the code above does not compile - the class graph is non-finitary. It is because:
Root[X] <: Template[X, Wrap[X]] <: Template[X, Root[Option[X]]]
For reasons unbeknownst to me, it is a good idea to expand the last type in the compiler by substituting Root[Option[X]] recursively with X. Now, if someone can explain why eager expanding of the subtype relation is done here, and not in Root[X] <: Template[X, Root[X]], for example, it would be great to know, but is not my main question here. Which is: how to refactor it?
I see two options:
1.
trait Template[X, +T[_]] {
def m :T[X]
}
This won't work, because the kind of the return type is not always the same: even adding a bound on a type parameter will break it.
The second alternative, are of course, member types:
trait Root[X] {
type Result <: Wrap[X]
def m :Wrap[X]
}
trait Sub[X] extends Root[Option[X]] {
type Result <: Wrap2[X]
}
But member types come always with one huge problem: the only possible way of achieving covariance is by declaring the return type Result as an upper bound. However, in that case, I cannot implement method m within that class in any other way than throwing an exception, because Result is unknown in that place. If I want to keep the hierarchy open for extension, for every interface trait I have to have a separate implementation class, i.e.
class RootImpl[X] extends Root[X] {
override type Result = Wrap[X]
override def m = new Wrap[X]
}
class SubImpl[X] extends Sub[X] {
override type Result = Wrap2[X]
override def m = new Wrap2[X]
}
Even if I extract the above bodies to mix in traits, so I don't have to cut and paste the implementation in every Root subclass which would like to use the default implementation,
it is still quite inconvenient compared to having the implementation within the actual interface traits.
Is there anything I didn't think of?
Would something like this work?
sealed trait Template[X, +T] {
def m(implicit ops: TemplateOps[X, T]): T = ops.m
}
sealed trait TemplateOps[X, +T] {
def m: T
}
object TemplateOps {
implicit def rootOps[X] = new TemplateOps[X, Option[X]] {
def m: Option[X] = ???
}
implicit def subOps[X] = new TemplateOps[X, X] {
def m: X = ???
}
}
class Root[T] extends Template [T,Option[T]
class Sub[T] extends Template [T,T]
Well, I have since thought of and tried another solution, so I thought I'd answer my question for posterity. It doesn't solve my real issue well, so I won't accept it though, but more on that later.
What I forgot, because as most people I do not really use structural types as I view them as a shabby solution, this actually might be a sensible, minimalistic application for them. Scala allows to refine a type not only by specifying its member types, but also methods (and values). We could get rid of Template completely and use plain hardcoded covariance:
trait BaseSignature {
def m :Any
}
trait Root[X] extends BaseSignature {
override def m = new Wrap[X]
}
trait Sub[X] extends Root[X] {
override def m = new Wrap2[X]
}
We simply use the whole signature of method m, including its return type, as part of a type we are interested in. A simplest example would be:
def wrap[R](x :BaseSignature { def m :R }) :X = x.m
wrap(new Root[Int]) //:Wrap1[X]
wrap(new Sub[Int]) //:Wrap2[X]
What's important to note, is that x.m call there is not reflective, as m method itself is declared in BaseSignature - scala compiler calls simply that and does a no-op cast to R. BaseSignature is necessary because of scala's annoying rule that an implemented method always overrides a declaration, even from the point of view of the caller,
so an argument of x :Root[X] { def m :R } would have x.m :Wrap1[X] and there is no way around it AFAIK.
This solution even works for reasonably complex method signatures, including generic methods with their own type parameters with bounds, as well as using type parameters of the enclosing class. Type inference in the latter case has serious issues though. Unfortunately, and I did not realise it until I coded the simpler methods, there is a problem with return types of higher kinds which depend on type arguments, or worse, on member types of value arguments. Consider instead:
trait Root[X] {
def m[A] = new Wrap1[A]
}
trait Sub[X] {
def m[A] = new Wrap3[A, X]
}
trait Wrap3[A, B]
The return type which we must infer here is a type constructor, and Scala 2 is very bad at that: it would infer R in m[A] :R[A] no problem in the first case, but in the case of Wrap2 only if A is the last argument, so, while simple cases might be covered, it is not a robust, generic solution. From what I read, Scala 3 could infer correctly R[A] =:= [A]Wrap3[X, A], so it might be good in the future.
For now, I would need to define an external type alias which guides the inferer:
trait Sub[X] extends Root[X] {
type SubResult[A] = Wrap3[X, A]
override def m[A] :SubResult[A] = new Wrap3[A]
}
Not ideal, but still better then the member type solution described earlier, as in this case any subclass Sub2 of Sub would just define its own type type Sub2Result[A] = ..., posing no conflict.
I might still stick with this as it looks like migration to Scala 3 will be the easiest, at the same time producing the simplest solution. It's still an interesting problem though, so I certainly would like to hear alternatives.

dealing with extractors on case classes with an invariant type parameter

I think, my issue is best described by some sample code:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X { def f: Foo[_] }
case class Wrapper[D](f: Foo[D]) extends X
val w: X = Wrapper(new Bar)
w match { case Wrapper(_: Bar) => 1 }
The last line fails with
found : Bar
required: Foo[Any]
Note: String <: Any (and Bar <: Foo[String]), but class Foo is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
I understand that this happens because unapply is defined with a type parameter, which is inferred as Any, and so it complains about String being incompatible.
But the question is if there is any way to make it work? I tried giving a type parameter to the exctractor, like this: w match { case Wrapper[String](_: Bar) => 1 }, but it says it does not take parameters (which is a lie) ... :(
The only way I came up with so far is this ugly baby:
w match { case w: Wrapper[String] if w.f.isInstanceOf[Bar] => 1 }
or, maybe,
Option(w).map(_.f) match { case Some(_: Bar) => 1 }
(the latter works because Option is covariant, but I can't make my classes covariant unfortunately). Also, I can't really use the last alternative without some additional ugliness IRL, because the equivalent of X in real life doesn't actually have f.
Any better ideas?
Define custom extractor
Wrapper.unapply does take type parameter, but you cannot specify one* in a pattern match sequence, so compiler infers one for you (and if compiler does that, it's very often Any or Nothing).
And, actually, you don't want it to, because you're removing your type information when you coerce your element to type X. So, you want a matcher for existential version
object WrapperEx {
def unapply(w: Wrapper[_]): Option[Foo[_]] = Wrapper.unapply(w)
}
And use it like so:
w match { case WrapperEx(_: Bar) => 1 }
Runnable version here
Good news: you can delegate to generated case class matcher.
Bad news: you cannot define it inside case class companion. Scala is happily picking the wrong one already, so it won't be able to disambiguate.
Still, I'd say it's not half-bad
* you can in latest Typelevel Scala, but I'm not sure how it works with type casts and I could not get it to work for your case.
You can parameterize trait X to get rid of existential type in def f: Foo[_], I think this is what trips the compiler up. The following code works:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X[A] { def f: Foo[A] }
case class Wrapper[D](f: Foo[D]) extends X[D]
val w: X[String] = Wrapper(new Bar) // type ascription can be omitted and will be inferred
w match { case Wrapper(_: Bar) => 1 }

Scala - Using complicated generics to get information of a subclass and another class - doesn't compile?

I am writing a program in scala that uses a framework around:
trait Tool[T <: Tool[T, U], U <: Settings[T]] {
// members here
def createSettingsFrom(settingsWithStringNames: Map[String, _]): U
}
trait Settings[T <: Tool[T, _ <: Settings[T]]
In Tool, T is the subclass, and U is a class that carries information for it. Each Tool can be regarded as a sort of command with parameters, and those parameters are custom for each of them.
I also have a class that extends it, along with its "information carrier":
object Cleanup extends Tool[Cleanup, CleanupSettings] {
override def createSettingsFrom(settings: Map[String, _]): CleanupSettings
= CleanupSettings(
settings.get("attribute1").asInstanceOf[Int]
settings.get("attribute2").asInstanceOf[String])
}
case class CleanupSettings extends Settings[Cleanup](
//attribute1: Int,
//attribute2: String
//more attributes)
When I attempt to compile these classes, I get the following stacktrace:
Information:21/10/16 03:20 - Compilation completed with 2 errors and 0 warnings in 3s 200ms
/project_folder/src/main/scala/io/oreville/maptools/operations/cleanup/Cleanup.scala
Error:(17, 24) type arguments [package.tools.operations.cleanup.Cleanup,package.tools.operations.cleanup.CleanupSettings] do not conform to trait ConfigurableTool's type parameter bounds [T <: package.tools.ConfigurableTool[T,U],U <: package.tools.params.Settings[T]]
object Cleanup extends ConfigurableTool[Cleanup, CleanupSettings] {
^
/project_folder/src/main/scala/io/oreville/maptools/operations/cleanup/CleanupSettings.scala
Error:(11, 11) type arguments [package.tools.operations.cleanup.Cleanup] do not conform to trait Settings's type parameter bounds [T <: package.tools.Tool[T, _ <: package.tools.params.Settings[T]]]
extends Settings[Cleanup]
^
I also have a trait ConfigurableTool which is just an extension of Tool with some extra functionality, so it has the exact same generic signature and it just extends Tool[T, U].
I tried multiple things to solve the problem including adding combinations of + and - to my generics for co- and contravariance, but it doesn't help.
I did consider using a Dynamic type for my settings, but speed is a bit of a factor. I don't even know if it would solve the problem if I did.
That's it really, I hope that you have some time to help my case, if not, thanks for reading anyway!
I wan't able to reproduce the error message you got.
There are a few typos in the code, but after cleaning them up, the only error left is in the definition of Cleanup which you cannot pass as a type parameter when extending a trait.
object Cleanup extends Tool[Cleanup.type, CleanupSettings] { ... }
illegal cyclic reference involving object Cleanup
You can get around that by making it a trait, and extending it in the companion object. Here's the full code:
trait Tool[T <: Tool[T, U], U <: Settings[T]] {
// members here
def createSettingsFrom(settingsWithStringNames: Map[String, _]): U
}
trait Settings[T <: Tool[T, _ <: Settings[T]]]
trait Cleanup extends Tool[Cleanup, CleanupSettings] {
override def createSettingsFrom(settings: Map[String, _]): CleanupSettings = CleanupSettings(
settings.get("attribute1").asInstanceOf[Int],
settings.get("attribute2").asInstanceOf[String])
}
object Cleanup extends Cleanup
case class CleanupSettings(
attribute1: Int,
attribute2: String) extends Settings[Cleanup]

Understanding the limits of Scala GADT support

The error in Test.test seems unjustified:
sealed trait A[-K, +V]
case class B[+V]() extends A[Option[Unit], V]
case class Test[U]() {
def test[V](t: A[Option[U], V]) = t match {
case B() => null // constructor cannot be instantiated to expected type; found : B[V] required: A[Option[U],?V1] where type ?V1 <: V (this is a GADT skolem)
}
def test2[V](t: A[Option[U], V]) = Test2.test2(t)
}
object Test2 {
def test2[U, V](t: A[Option[U], V]) = t match {
case B() => null // This works
}
}
There are a couple ways to make the error change, or go away:
If we remove the V parameter on trait A (and case class B), the 'GADT-skolem' part of the error goes away but the 'constructor cannot be instantiated' part remains.
If we move the U parameter of the Test class to the Test.test method, the error goes away. Why ? (Similarly, the error is not present in Test2.test2)
The following link also identifies that problem, but I do not understand the provided explanation. http://lambdalog.seanseefried.com/tags/GADTs.html
Is this an error in the compiler ? (2.10.2-RC2)
Thank you for any help with clarifying that.
2014/08/05: I have managed to further simplify the code, and provide another example where U is bound outside the immediate function without causing a compilation error. I still observe this error in 2.11.2.
sealed trait A[U]
case class B() extends A[Unit]
case class Test[U]() {
def test(t: A[U]) = t match {
case B() => ??? // constructor cannot be instantiated to expected type; found : B required: A[U]
}
}
object Test2 {
def test2[U](t: A[U]) = t match {
case B() => ??? // This works
}
def test3[U] = {
def test(t: A[U]) = t match {
case B() => ??? // This works
}
}
}
Simplified like that this looks more like a compiler bug or limitation. Or am I missing something ?
Constructor patterns must conform to the expected type of the pattern, which means B <: A[U], a claim which is true if U is a type parameter of the method presently being called (because it can be instantiated to the appropriate type argument) but untrue if U is a previously bound class type parameter.
You can certainly question the value of the "must conform" rule. I know I have. We generally evade this error by upcasting the scrutinee until the constructor pattern conforms. Specifically,
// Instead of this
def test1(t: A[U]) = t match { case B() => ??? }
// Write this
def test2(t: A[U]) = (t: A[_]) match { case B() => ??? }
Addendum: in a comment the questioner says "The question is simple on why it doesn't works with type parameter at class level but works with at method level." Instantiated class type parameters are visible externally and have an indefinite lifetime, neither of which is true for instantiated method type parameters. This has implications for type soundness. Given Foo[A], once I'm in possession of a Foo[Int] then A must be Int when referring to that instance, always and forever.
In principle you could treat class type parameters similarly inside a constructor call, because the type parameter is still unbound and can be inferred opportunistically. That's it, though. Once it's out there in the world, there's no room to renegotiate.
One more addendum: I see people doing this a lot, taking as a premise that the compiler is a paragon of correctness and all that's left for us to do is ponder its output until our understanding has evolved far enough to match it. Such mental gymnastics have taken place under that tent, we could staff Cirque de Soleil a couple times over.
scala> case class Foo[A](f: A => A)
defined class Foo
scala> def fail(foo: Any, x: Any) = foo match { case Foo(f) => f(x) }
fail: (foo: Any, x: Any)Any
scala> fail(Foo[String](x => x), 5)
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at $anonfun$1.apply(<console>:15)
at .fail(<console>:13)
... 33 elided
That's the current version of scala - this is still what it does. No warnings. So maybe ask yourselves whether it's wise to be offering the presumption of correctness to a language and implementation which are so trivially unsound after more than ten years of existence.
Looks like it is a compiler caveat. From this, martin odersky puts it as:
In the method case, what you have here is a GADT:
Patterns determine the type parameters of an corresponding methods in the scope of a pattern case.
GADTs are not available for class parameters.
As far as I know, nobody has yet explored this combination, and it looks like it would be quite tricky to get this right.
PS: Thanks to #retronym who provided the reference from discussion here
On why it throws an error in case of class:
This works:
sealed trait A[-K, +V]
case class B[+V]() extends A[Option[Unit], V]
case class Test[U]() {
def test[V, X <: Unit](t: A[Option[X], V]) = t match {
case B() => null
}
def test2[V](t: A[Option[U], V]) = Test2.test2(t)
}
object Test2 {
def test2[U, V](t: A[Option[U], V]) = t match {
case B() => null // This works
}
}
To examplain why the compiler threw an error: Try doing this:
scala> :paste
// Entering paste mode (ctrl-D to finish)
abstract class Exp[A]{
def eval:A = this match {
case Temp(i) => i //line-a
}
}
case class Temp[A](i:A) extends Exp[A]
// Exiting paste mode, now interpreting.
To understand this, from §8.1.6: above Temp is a polymorphic type. If the case class is polymorphic then:
If the case class is polymorphic, then its type parameters are
instantiated so that the instantiation of c conforms to the expected
type of the pattern. The instantiated formal parameter types of c’s
primary constructor are then taken as the expected types of the
component patterns p 1 , . . . , p n . The pattern matches all objects
created from constructor invocations c(v1 , . . . , v n ) where each
element pattern p i matches the corresponding value v i .
i.e. the compiler smartly tries to instantiate Temp in line-a such that it conforms to the primary constructor of this( which if above compiled successfully then in case of Temp(1) would be something like Exp[Int] and hence compiler instantiates Temp in line-a with parameter as Int.
Now in our case: the compiler is trying to instantiate B. It sees that t is of type A[Option[U],V] where U is already fixed and obtained from class parameter and V is generic type of method. On trying to initialize B it tries to create in such a way that it ultimately gets A[Option[U],V]. So with B() it is somehow trying to get A[Option[U],V]. But it cant as B is A[Option[Unit],V]. Hence it ultimately cannot initialize B. Fixing this makes it work
Its not required in case of test-2 because: The compiler as explained in above process is trying to initialize type parameter of B. It knows t has type parameter [Option[U], V] where U and V are both generic wrt method and are obtained from argument. It tries to initialize B based on the agrument. If the argument was new B[String] it tries deriving B[String] and hence U is automatically obtained as Option[Unit]. If the argument was new A[Option[Int],String] then it obviously it wont match.
The difference has to do with what information the compiler and runtime have in each case, combined with what the restrictions on the types are.
Below the ambiguity is clarified by having U be the trait and class parameter, and X be the method type paramter.
sealed trait A[U]
case class B(u: Unit) extends A[Unit]
class Test[U]() {
def test(t: A[U]) = t match {
case B(u) => u // constructor cannot be instantiated to expected type; found : B required: A[U]
}
}
object Test2 {
def test2[X](t: A[X]) = t match {
case B(x) => x // This works
}
def test3[X] = {
def test(t: A[X]) = t match {
case B(x) => x // This works
}
}
}
Test2.test2(new B(println("yo")))
In Test2.test2, the compiler knows that an instance of A[X] will be provided, with no limits on X.It can generate code that inspects the parameter provided to see if it is B, and handle the case.
In class Test method test, the compiler knows not that some A[X] is provided when called, but that some specific type, U, is presented. This type U can be anything. However, the pattern match is on an algebraic data type. This data type has exactly one valid variation, of type A[Unit]. But this signature is for A[U] where U is not limited do Unit. This is a contradiction. The class definition says that U is a free type parameter, the method says it is Unit.
Imagine you instantiate Test:
val t = new Test[Int]()
Now, at the use site, the method is:
def test(t: A[Int])
There is no such type A[Int]. Pattern matching on B is when the compiler inspects this condition. A[U] for any U is incompatible with the type, hence "constructor cannot be instantiated to expected type; found : B required: A[U]"
The difference with the method versus class type parameter is that one is bound while one is free, at the time the method is called in the runtime.
Another way to think about it is that defining the method
def test(t: A[U])
implies that U is Unit, which is inconsistent with the class declaration that U is anything.
Edit: this is not an answer to the question. I'm keeping it for reference (and the comments).
The link you provided already gives the answer.
In the case of the parameterised method, U is infered from the argument of the actual method call. So, the fact that the case B() was chosen implies that U >: Unit (otherwise the method could not have been called with a B) and the compiler is happy.
In the case of the parameterized class, U is independent from the method argument. So, the fact that the case B() was chosen tells the compiler nothing about U and it can not confirm that U >: Unit. I think if you add such a type bound to U it should work. I haven't tried it, though.
The compiler is absolutely correct with its error message. An easy example should describe the issue well:
sealed trait A[U]
class B extends A[Unit]
class T[U] {
val b: A[Unit] = new B
f(b) // invalid, the compiler can't know which types are valid for `U`
g(b) // valid, the compiler can choose an arbitrary type for `V`
def f(t: A[U]) = g(t)
def g[V](t: A[V]) = ???
}
f and g have different type signatures. g takes an A[V] where V is an arbitrary type parameter. On the other hand, f takes an A[U] where U is not arbitrary but dependent from the outer class T.
The compiler does not know what type U can be at the moment when it typechecks T - it needs to typecheck an instantiation of T to find out which types are used. One can say that U is a concrete type inside of T, but a generic type outside of it. This means it has to reject every code fragment that gets inside of T more concrete about the type of U.
Calling g from inside f is clearly allowed - after all an arbitrary type for V can be chosen, which is U in this case. Because U is never again referenced in g it doesn't matter what type it may have.
Your other code example underlies the same limitations - it is just an example that is more complex.
Btw, that this code
def test3[U] = {
def test(t: A[U]) = t match {
case B() => ??? // This works
}
}
is valid looks weird to me. It doesn't matter if U is bound by a class or a method - from inside of the binding scope we can't give any guarantees about its type, therefore the compiler should reject this pattern match too.

Is it possible to refer to the types of Scala case class constructor arguments?

My goal is to create a trait that a case class can extend, which can process each constructor argument and then pass them as arguments to a method of the case class. All of the constructor arguments will be the same type with different type parameters, and the method will take arguments that match the type parameter of each constructor argument. You can think of it as a pre-processor of the constructor arguments. For example,
case class Foo(input1:Input[Int], input2:Input[String]) extends MagicTrait {
def run(input1:Int, input2:String) { ... }
}
Is this at all feasible? Is it feasible in a way that isn't terribly ugly (e.g. all reflection)? Is it possible to refer to the companion object of a case class in a way that is at all generic across case classes (e.g. a function that takes the output of Companion.unapply())?
Seeing as an acceptable solution allows the preprocessing functionality to be moved off the instances to an associated object the main remaining difficulty is that you want to be able to abstract over the arity and types (ie. the shape) of your case class constructors. This is possible with the HList implementation and polymorphic function values from shapeless.
First some preliminaries,
import shapeless.HList._
import shapeless.Functions._
import shapeless.Poly._
import shapeless.TypeOperators._
// Implementation of your Input wrapper
case class Input[T](value: T)
// Value extractor as a shapeless polymorphic function value
object value extends (Input ~> Id) {
def default[T](i : Input[T]) = i.value
}
We can now define a preprocessor base class which provides an apply method which takes an HList of Input types, maps the value polymorphic function across it (ie. performing the preprocessing) and then passes the resulting HList of non-Input types to the provided case class constructor (which is given in hlisted form, see below),
// Pre-processer base class
abstract class Preprocessor[In <: HList, Out <: HList, R](ctor : Out => R)
(implicit mapper : MapperAux[value.type, In, Out]) {
def apply(in : In) = ctor(in map value)
}
Now we define the case class with the post-processing component types,
case class Foo(input1 : Int, input2 : String)
and add one line of boilerplate,
object FooBuilder extends Preprocessor((Foo.apply _).hlisted)
(here the Foo companion object factory method is provided as the Preprocessor constructor argument in HListed form as required above.)
Now we can construct Foo instances using the FooBuilder.
val foo = FooBuilder(Input(23) :: Input("foo") :: HNil)
Unfortunately it isn't (currently) possible to combine the FooBuilder object with the Foo companion object: if you attempt to have the Foo companion extend Preprocessor you'll discover that the Foo factory method isn't available to be passed as the Preprocessor constructor argument.
To illustrate that this solution is really abstracting over type and arity, here's how we might add a second differently shaped case class,
case class Bar(input1 : Int, input2 : String, input3 : Boolean)
object BarBuilder extends Preprocessor((Bar.apply _).hlisted)
val bar = BarBuilder(Input(23) :: Input("foo") :: Input(true) :: HNil)
case class Input[T](value: T)
trait MagicTrait[T,U] {
val input1: Input[T]
val input2: Input[U]
def run: Unit
}
case class Foo(input1: Input[Int], input2: Input[String])
extends MagicTrait[Int, String] {
def run = println(input1.value * 2 + input2.value.toUpperCase)
}
scala> val m: MagicTrait[_,_] = Foo(Input(3), Input("hi"))
m: MagicTrait[_, _] = Foo(Input(3),Input(hi))
scala> m.run
6HI
edit:
If you want to find the types of the class parameters you can use the fact that case classes extend Product:
scala> Foo(2, "hi").productIterator.map(_.asInstanceOf[AnyRef].getClass).toList
res13: List[java.lang.Class[_]] =
List(class java.lang.Integer, class java.lang.String)
But this uses the reflection you wanted to avoid. This is why we use parameterization.
If you want to return its companion object, I'm not sure you can do this in a useful, type-safe way in the context of case classes, because companion objects don't extend an interface that specifies their extractor methods. You might be able to do something with structural types but it's probable that there's a better way to approach whatever problem it is that you're trying to solve.