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:
- I would like to type-check that
MapOutput
is of the typeKeyVal[K, C]
, - I want to type-check that
C
is the same type used inf
, - I also need to "extract"
K
in order to instantiatemem
, and type-check parameters fromapply
.
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:
As you can see,
ReducerComponent
has appropriate type. Implicit evidence is used here to catchK
andV
from yourM <: KeyVal[_, _]
.P.S. The version above requires to specify parameter types explicitly for your
f
, like(_: Double) + (_: Double)
. If you want to avoid this:Update. If you want to generelize keyval - use type function:
But keep in mind that
apply
from your question still takesKeyVal[K,V]
.You can also pass
KV
into some class: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:
Now you can create a factory for
ReducerComponent
: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 ofKeyValAux
(maybe another name is more appropriate for your use case):UPDATE
As @dk14 mentions in the comments, if you still want the type-parameter syntax, you could do the following:
You can then write:
Alternatively, you can write
OutputSpec
as a type function:This will not generate an additional unused class.