以下Scala代码有效:
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)
我的问题是我想像这样实例化ReducerComponent
:
val red = new ReducerComponent[MapOutput, Int]((a: Int, b: Int) => a + b)
甚至更好:
val red = new ReducerComponent[MapOutput](_ + _)
这意味着很多事情:
MapOutput
类型KeyVal[K, C]
,C
与f
中使用的相同类型,K
以实例化mem
,并从apply
输入类型检查参数。要问很多吗? :)我想写点像
class ReducerComponent[KeyVal[K,V]](f: (V, V) => V) {...}
到时我将ReducerComponent
实例化f
MapOutput
和KeyVal[K,V]
,所以推断V是可以的。但是我只有KeyVal[_,_]
作为类的参数,可以与{{1}}不同。
如果您了解类型推断是如何工作的,我知道我问的内容可能很疯狂,但我不知道!而且我甚至不知道什么是一个好的方法继续 - 除了在我的高级代码中一直提出明确的类型声明。我应该改变所有架构吗?
答案 0 :(得分:5)
写一个简单的工厂:
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
如您所见,ReducerComponent
具有适当的类型。此处使用隐式证据来捕获K
中的V
和M <: KeyVal[_, _]
。
P.S。上述版本要求为f
明确指定参数类型,例如(_: Double) + (_: Double)
。如果你想避免这种情况:
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
更新。如果你想使用keyval使用类型函数:
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)
}
}
但请注意,您问题中的apply
仍需KeyVal[K,V]
。
您也可以将KV
传递到某个类:
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
答案 1 :(得分:4)
您将需要与路径相关的类型。我推荐以下内容:
首先,编写一个具有相关类型作为成员的特征,以便在定义中访问它们:
trait KeyValAux {
type K
type V
type KV = KeyVal[K, V]
}
现在您可以为ReducerComponent
:
object ReducerComponent {
def apply[T <: KeyValAux](f: (T#V, T#V) => T#V) =
new ReducerComponent[T#K, T#V](f)
}
请注意,在这里,我们可以简单地访问该类型的成员。我们无法对类型参数执行此操作。
现在,根据MapOutput
定义您的KeyValAux
(或许其他名称更适合您的用例):
type MapOutput = KeyValAux { type K = String; type V = Int }
def mapFun(s:String): MapOutput#KV = KeyVal(s, 1)
val red = ReducerComponent[MapOutput](_ + _)
<强>更新强>
正如@ dk14在评论中提到的那样,如果您仍然需要类型参数语法,则可以执行以下操作:
trait OutputSpec[KK, VV] extends KeyValAux {
type K = KK
type V = VV
}
然后你可以写:
type MapOutput = OutputSpec[String, Int]
或者,您可以将OutputSpec
写为类型函数:
type OutputSpec[KK, VV] = KeyValAux { type K = KK; type V = VV }
这不会产生额外的未使用的类。