How to “extract” type parameter to instantiate another class

前端 未结 2 1701
别那么骄傲
别那么骄傲 2021-01-03 08:19

The following Scala code works:

object ReducerTestMain extends App {

  type MapOutput = KeyVal[String, Int]

  def mapFun(s:String): MapOutput = KeyVal(s, 1         


        
相关标签:
2条回答
  • 2021-01-03 09:02

    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.

    0 讨论(0)
  • 2021-01-03 09:06

    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
    
    0 讨论(0)
提交回复
热议问题