【问题标题】:How to "extract" type parameter to instantiate another class如何“提取”类型参数以实例化另一个类
【发布时间】:2015-06-27 13:20:51
【问题描述】:

以下 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. 我想检查C 是否与f 中使用的类型相同,
  3. 我还需要“提取”K 以实例化mem,并从apply 中检查参数。

要问的很多吗? :) 我想写类似的东西

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

到我将​​实例化ReducerComponent 时,我所拥有的只有fMapOutput,所以推断V 是可以的。但是我只有KeyVal[K,V]作为类的类型参数,它可以不同于KeyVal[_,_]

如果您了解类型推断的工作原理,我知道我的问题可能很疯狂,但我不知道!而且我什至不知道什么是继续的好方法——除了在我的高级代码中一直进行显式类型声明。我应该改变所有的架构吗?

【问题讨论】:

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


    【解决方案1】:

    为此,您将需要依赖路径的类型。我推荐以下:

    首先,编写一个将您的相关类型作为成员的特征,以便您可以在定义中访问它们:

    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)
    }
    

    注意,在这里,我们可以简单地访问类型的成员。我们不能对类型参数执行此操作。

    现在,根据KeyValAux 定义您的MapOutput(也许另一个名称更适合您的用例):

    type MapOutput = KeyValAux { type K = String; type V = Int }
    
    def mapFun(s:String): MapOutput#KV = KeyVal(s, 1)
    
    val red = ReducerComponent[MapOutput](_ + _)
    

    更新

    正如@dk14 在 cmets 中提到的,如果您仍然想要类型参数语法,您可以执行以下操作:

    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 }
    

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

    【讨论】:

    • 最好定义trait KeyValAux[KK, VV] { type K = KK; type V = VV; type KV = KeyVal[K, V] } 以使MapOutput 定义更好一点:type MapOutput = KeyValAux[String, Int]
    • 那就没有办法写ReducerComponent.apply了。但是我们可以写一个子类来做这件事。
    【解决方案2】:

    只写一个简单的工厂:

    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 具有适当的类型。这里使用隐含证据从您的M &lt;: KeyVal[_, _] 中捕获KV

    附:上面的版本需要为您的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,因为它不需要类型成员(这往往会使人们感到困惑)。
    • 其实我有编译错误type arguments [A$A292.this.MapOutput] do not conform to method apply's type parameter bounds [M &lt;: Map[_, _]]
    • @dk14 上一个出厂版本正常工作。干得好!
    • 好吧,这看起来很有希望,但在我的实际问题中,我所拥有的是object ReducerTest[MapOutput] { def buildReducer(f)...},而这个MapOutput 可能与KeyVal[_,_] 不同。在这种情况下,我希望 buildReducer 方法失败,但我仍然应该能够创建 ReducerTest 对象。 Scala 显然编译失败,说它不能证明 MapOutput =:= KeyVal 总是。
    • 我无法适应我的实际问题。这是我所拥有的,class MyBuilder[A](mystate:Sometype) { def buildMapper[B](f: A=&gt;B)=MyBuilder[B](Map(f)::state); def buildReducer[V](f: (V,V)=&gt;V)=MyBuilder[V](Red(f)::state); }“状态”是经过类型检查的工人链。它适用于buildMapper,类类型参数存储最后一个函数返回类型。但是对于reducer A 应该持有一些KeyVal[K,V]。 implicit 适用于特定类型,但我无法让类型函数工作......我也许应该创建两个姐妹 Builder 类?
    猜你喜欢
    • 2010-12-23
    • 1970-01-01
    • 2022-08-10
    • 1970-01-01
    • 2016-11-16
    • 1970-01-01
    • 1970-01-01
    • 2014-06-21
    • 2011-09-19
    相关资源
    最近更新 更多