Hello,
I am attempting to express a set of “constraints” that must be processed into a “function” that uses those constraints to generate values. An example will make it easier to understand. Say I have these set of “constraints”:
sealed trait Param[T]
final case class Range[T](start: T, end: T, delta: T)(implicit num: Numeric[T]) extends Param[T]
final case class Options[T](e: Seq[T]) extends Param[T]
final case class Composite[T1,T2](paramA: Param[T1], paramB: Param[T2]) extends Param[(T1,T2)]
Now I can construct a search space so:
val p1: Range[Int] = Range(1, 10, 1)
val p2: Options[String] = Options(List("a", "b", "c"))
val p3: Composite[Int, String] = Composite(p1, p2)
The idea is then to have several strategies that can be “applied” to these constraints to convert them into a sampler (that generates values according to those constraints). For example I could have:
def gridSearch[T](p: Param[T]): Sample[T] = p match {
case g@Range(_, _, delta) =>
val r: Sample[T] = linear(g, delta)
r
case l@Options(_) =>
val r = list(l)
r
case Composite(paramA, paramB) =>
val a = gridSearch(paramA)
val b = gridSearch(paramB)
val c = cartesian(a, b)
c
}
I could then use this so:
val s1: Sample[Int] = gridSearch(p1)
val s2: Sample[String] = gridSearch(p2)
val s3: Sample[(Int, String)] = gridSearch(p3)
val s4: Sample[(Int, (Int, String))] = gridSearch(Composite(p1,p3))
Notice that the return type is a “compound tuple” that will then be used as a function parameter elsewhere.
All seems to be fine and dandy as long as the functions linear, list and cartesian are also generic. However, my problem is that functions like linear and list are not (but cartesian is).
More concretely: in the example above p1 is of type Range[Int]and results in a Sample[Int] but it could be of any other type. The following is a “complete” example showing the issue:
object Exp7c {
sealed trait Sampler[T]
case class IntUser(s:Int,e:Int) extends Sampler[Int]
case class DoubleUser(s:Double,e:Double) extends Sampler[Double]
sealed trait Param[T]
final case class Range[T](start: T, end: T, delta: T) extends Param[T]
final case class Options[T](e: Seq[T]) extends Param[T]
final case class Composite[T1,T2](paramA: Param[T1], paramB: Param[T2]) extends Param[(T1,T2)]
val p1: Range[Int] = Range(1, 10, 1)
val p2: Options[String] = Options(List("a", "b", "c"))
val p3: Composite[Int, String] = Composite(p1, p2)
def useInt(p : Range[Int]): Sampler[Int] = IntUser(p.start, p.end)
def useDouble(p : Range[Double]): Sampler[Double] = DoubleUser(p.start, p.end)
def process[T](p: Param[T]): Sampler[T] = p match {
case g@Range[Int](_, _, delta) =>
val r = useInt(g)
r
case g@Range[Double](_, _, delta) =>
val r = useDouble(g)
r
}
}
Of course process does not compile. So my question is, short of creating each Param[T] for a specific type, what is the best way to model this in Scala. Any pointers or examples will be greatly appreciated.