The following Scala code works:
object ReducerTestMain extends App {
type MapOutput = KeyVal[String, Int]
def mapFun(s:String): MapOutput = KeyVal(s, 1)
val red = new ReducerComponent[String, Int]((a: Int, b: Int) => a + b)
val data = List[String]("a", "b", "c", "b", "c", "b")
data foreach {s => red(mapFun(s))}
println(red.mem)
// OUTPUT: Map(a -> 1, b -> 3, c -> 2)
}
class ReducerComponent[K, V](f: (V, V) => V) {
var mem = Map[K, V]()
def apply(kv: KeyVal[K, V]) = {
val KeyVal(k, v) = kv
mem += (k -> (if (mem contains k) f(mem(k), v) else v))
}
}
case class KeyVal[K, V](key: K, value:V)
My problem is I would like to instantiate ReducerComponent
like this:
val red = new ReducerComponent[MapOutput, Int]((a: Int, b: Int) => a + b)
or even better:
val red = new ReducerComponent[MapOutput](_ + _)
That means a lot of things:
MapOutput
is of the type KeyVal[K, C]
,C
is the same type used in f
,K
in order to instantiate mem
, and type-check parameters from apply
.Is it a lot to ask? :) I wanted to write something like
class ReducerComponent[KeyVal[K,V]](f: (V, V) => V) {...}
By the time I will instantiate ReducerComponent
all I have is f
and MapOutput
, so inferring V is OK. But then I only have KeyVal[K,V]
as a type parameter from a class, which can be different from KeyVal[_,_]
.
I know what I'm asking is probably crazy if you understand how type inference works, but I don't! And I don't even know what would be a good way to proceed --- apart from making explicit type declarations all the way up in my high-level code. Should I just change all the architecture?
Just write a simple factory:
case class RC[M <: KeyVal[_, _]](){
def apply[K,V](f: (V,V) => V)(implicit ev: KeyVal[K,V] =:= M) = new ReducerComponent[K,V](f)
}
def plus(x: Double, y: Double) = x + y
scala> RC[KeyVal[Int, Double]].apply(plus)
res12: ReducerComponent[Int,Double] = ReducerComponent@7229d116
scala> RC[KeyVal[Int, Double]]()(plus)
res16: ReducerComponent[Int,Double] = ReducerComponent@389f65fe
As you can see, ReducerComponent
has appropriate type. Implicit evidence is used here to catch K
and V
from your M <: KeyVal[_, _]
.
P.S. The version above requires to specify parameter types explicitly for your f
, like (_: Double) + (_: Double)
. If you want to avoid this:
case class RC[M <: KeyVal[_, _]](){
def factory[K,V](implicit ev: KeyVal[K,V] =:= M) = new {
def apply(f: (V,V) => V) = new ReducerComponent[K,V](f)
}
}
scala> RC[KeyVal[Int, Double]].factory.apply(_ + _)
res5: ReducerComponent[Int,Double] = ReducerComponent@3dc04400
scala> val f = RC[KeyVal[Int, Double]].factory
f: AnyRef{def apply(f: (Double, Double) => Double): ReducerComponent[Int,Double]} = RC$$anon$1@19388ff6
scala> f(_ + _)
res13: ReducerComponent[Int,Double] = ReducerComponent@24d8ae83
Update. If you want to generelize keyval - use type function:
type KV[K,V] = KeyVal[K,V] //may be anything, may implement `type KV[K,V]` from some supertrait
case class RC[M <: KV[_, _]](){
def factory[K,V](implicit ev: KV[K,V] =:= M) = new {
def apply(f: (V,V) => V) = new ReducerComponent[K,V](f)
}
}
But keep in mind that apply
from your question still takes KeyVal[K,V]
.
You can also pass KV
into some class:
class Builder[KV[_,_]] {
case class RC[M <: KV[_, _]](){
def factory[K,V](implicit ev: KV[K,V] =:= M) = new {
def apply(f: (V,V) => V) = new ReducerComponent[K,V](f)
}
}
}
scala> val b = new Builder[KeyVal]
scala> val f = b.RC[KeyVal[Int, Double]].factory
scala> f(_ + _)
res2: ReducerComponent[Int,Double] = ReducerComponent@54d9c993
You will need path-dependent types for this. I recommend the following:
First, write a trait that has your relevant types as members, so you can access them within definitions:
trait KeyValAux {
type K
type V
type KV = KeyVal[K, V]
}
Now you can create a factory for ReducerComponent
:
object ReducerComponent {
def apply[T <: KeyValAux](f: (T#V, T#V) => T#V) =
new ReducerComponent[T#K, T#V](f)
}
Note that here, we can simply access the members of the type. We can't do this for type parameters.
Now, define your MapOutput
in terms of KeyValAux
(maybe another name is more appropriate for your use case):
type MapOutput = KeyValAux { type K = String; type V = Int }
def mapFun(s:String): MapOutput#KV = KeyVal(s, 1)
val red = ReducerComponent[MapOutput](_ + _)
UPDATE
As @dk14 mentions in the comments, if you still want the type-parameter syntax, you could do the following:
trait OutputSpec[KK, VV] extends KeyValAux {
type K = KK
type V = VV
}
You can then write:
type MapOutput = OutputSpec[String, Int]
Alternatively, you can write OutputSpec
as a type function:
type OutputSpec[KK, VV] = KeyValAux { type K = KK; type V = VV }
This will not generate an additional unused class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With