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.