如何“提取”类型参数来实例化另一个类

时间:2015-04-20 17:18:16

标签: scala type-inference type-parameter scala-generics

以下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](_ + _)

这意味着很多事情:

  1. 我想输入MapOutput类型KeyVal[K, C]
  2. 我想要检查Cf中使用的相同类型,
  3. 我还需要“提取”K以实例化mem,并从apply输入类型检查参数。
  4. 要问很多吗? :)我想写点像

    class ReducerComponent[KeyVal[K,V]](f: (V, V) => V) {...}
    

    到时我将ReducerComponent实例化f MapOutputKeyVal[K,V],所以推断V是可以的。但是我只有KeyVal[_,_]作为类的参数,可以与{{1}}不同。

    如果您了解类型推断是如何工作的,我知道我问的内容可能很疯狂,但我不知道!而且我甚至不知道什么是一个好的方法继续 - 除了在我的高级代码中一直提出明确的类型声明。我应该改变所有架构吗?

2 个答案:

答案 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中的VM <: 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 }

这不会产生额外的未使用的类。