【问题标题】:In Scala, how to infer one type parameters of a def?在 Scala 中,如何推断 def 的一种类型参数?
【发布时间】:2017-12-15 17:17:49
【问题描述】:

我有一个类似的问题(In Scala, is it possible to “curry” type parameters of a def?) ,但我不知道如何使用给定的解决方案解决它。

如下所示,我目前的实现不允许类型参数的推断(需要提供泛型类型U)。

trait Block[U] {
  def map(df: DataFrame, params: U): DataFrame
}

case class ParseURL() extends Block[(String, Column)] {
  override def map(df: DataFrame, params: (String, Column)): DataFrame
}

class Pipeline(df: Dataframe) {
  ...
  def copy(newDf: DataFrame) = new Pipeline(newDf)
  ...

  def map[T <: Block[U] : ClassTag, U](d: U): Pipeline = {
    val block: T = implicitly[ClassTag[T]].runtimeClass.newInstance.asInstanceOf[T]
    this.copy(block.map(df, d))
  }
  ...
}

这是我目前对该实现的使用:

val pipeline = new Pipeline(df).map[ParseURL, (String, Column)]("url", $"url")

但是我想用map方法比如:

val pipeline = new Pipeline(df).map[ParseURL]("url", $"url")

我认为匿名课程可能是可能的,但我们将不胜感激:)

编辑:另外,我不知道this article 是否应该激励我。

【问题讨论】:

    标签: scala


    【解决方案1】:

    有一种方法可以得到与您正在寻找的东西相似的东西,但它可能会让读者有点笨拙和困惑,因为它使最终调用 map 看起来像这个:.map(("url", col("url")))[ParseURL].

    这里的想法是创建一个从map(这里称为Mapper)返回的中间类,它保存U类型信息,然后有一个无参数apply方法接受T类型参数:

    class Pipeline(df: DataFrame) { self =>
      def copy(newDf: DataFrame) = new Pipeline(newDf)
    
      final class Mapper[U](d: U) {
        def apply[T <: Block[U] : ClassTag]: Pipeline = {
          val block: T = implicitly[ClassTag[T]].runtimeClass.newInstance.asInstanceOf[T]
          self.copy(block.map(df, d))
        }
      }
    
      def map[U](d: U): Mapper[U] = new Mapper(d)
    }
    
    val pipeline = new Pipeline(df).map(("url", col("url")))[ParseURL]
    

    它看起来确实很奇怪,所以要么接受,要么放弃:)

    一个轻微的替代方法是将apply 重命名为其他名称,例如using,这样会更长但可能更清晰:

    val pipeline = new Pipeline(df).map(("url", col("url"))).using[ParseURL]
    

    【讨论】:

    • 感谢您的回复 :) 但是编译器抱怨参数:type arguments [ssp.pipeline.blocks.ParseURL] do not conform to method using's type parameter bounds [T &lt;: ssp.pipeline.blocks.Block[(String, org.apache.spark.sql.ColumnName)]]
    • 感谢您对提供更好的开发体验的建议。实际上,这对我来说意义重大,因为我的同事会经常使用它(我希望如此......)。
    • 编译器错误是$"url" 具有类型ColumnName(扩展Column)和ParseURL 期望Column 的结果。您可以通过使用等效的col("url") 或将ParseURL 更改为使用ColumnName(我不推荐)或通过定义BlockU 作为逆变类型来解决此问题:@ 987654343@,这意味着Block[(String, Column)] 扩展了Block[(String, ColumnName)],因此当using 期望Block[(String, ColumnName)] 时,传递ParseURL 将起作用。希望这是有道理的......
    • 是的,逆变类型似乎是最好的解决方案。非常感谢:)
    【解决方案2】:

    我认为您不能轻易地在引用的问题中应用该解决方案,因为您的类型 TU 之间存在依赖关系,并且它朝着错误的方向发展:T 取决于 U 而您想省略U

    这是另一个可能对您有所帮助的选项。它基于将implicitly 调用替换为显式参数的想法,该参数将为编译器提供类型信息。这个想法是引入BlockFactory trait,例如:

    trait Block[U] {
      def map(df: DataFrame, params: U): DataFrame
    }
    
    trait BlockFactory[T <: Block[U], U] {
      def create(): T
    }
    
    class ParseURL extends Block[(String, Column)] {
      override def map(df: DataFrame, params: (String, Column)): DataFrame = ???
    }
    
    object ParseURL extends BlockFactory[ParseURL, (String, Column)] {
      override def create(): ParseURL = new ParseURL
    }
    
    
    class Pipeline(df: DataFrame) {
      //      ...
      def copy(newDf: DataFrame) = new Pipeline(newDf)
    
      //      ...
    
      def map[T <: Block[U] : ClassTag, U](blockFactory: BlockFactory[T, U], d: U): Pipeline = {
        val block: T = blockFactory.create()
        this.copy(block.map(df, d))
      }
    
    
      //      ...
    }
    

    所以你可以把它当作

    val pipeline = new Pipeline(df).map(ParseURL, ("url", $"url"))  
    

    如果您的典型Block 实现实际上是非泛型的,就像ParseURL 一样,这个想法应该可以工作。如果你有一些通用的Block 实现,那么用法看起来就不那么好了:

    class GenericBlock[U] extends Block[U] {
      override def map(df: DataFrame, params: U): DataFrame = ???
    }
    
    class GenericBlockFactory[U] extends BlockFactory[GenericBlock[U], U] {
      override def create(): GenericBlock[U] = ???
    }
    
    object GenericBlockFactory {
      def apply[U](): GenericBlockFactory[U] = new GenericBlockFactory[U]
    }
    
    val pipelineGen = new Pipeline(df).map(GenericBlockFactory[(String, Column)](), ("url", $"url"))
    

    您可以通过颠倒map 的参数顺序,然后对其进行currying,例如

    class Pipeline(df: DataFrame) {
    
      def map[T <: Block[U] : ClassTag, U](d: U)(blockFactory: BlockFactory[T, U]): Pipeline = 
    
    }
    
    val pipelineGen = new Pipeline(df).map(("url", $"url"))(GenericBlockFactory())
    

    这样,您不必为GenericBlockFactory 指定泛型类型,仍然必须编写() 来调用它的apply。这种方式对我来说感觉不太自然,但你可以节省一些打字。

    【讨论】:

    • 感谢您的回复。工厂的想法很有趣,但与第一个答案相比似乎有点过分了。我在下面的回答也一样,我认为调试机制会更复杂。
    【解决方案3】:

    实际上,我的第一个实现是创建一个可以在我的管道类中重复使用的块注册表。但是正如你所看到的,这个解决方案对我来说并不完美,因为我必须明确地注册我的块。而且我更喜欢避免冗余。

    trait Block {
      type Parameters
    
      // WARNING: This function is used only by pipeline and cast only the block parameters to avoid any cast in
      // implementations
      def mapDf[T <: Block : ClassTag](df: DataFrame, params: Any): DataFrame = {
        this.map[T](df, params.asInstanceOf[Parameters])
      }
    
      // Abstract function that processes a dataframe
      def map[T <: Block : ClassTag](df: DataFrame, params: Parameters): DataFrame
    }
    
    case class ParseURL() extends Block {
      override type Parameters = (String, Column)
    
      override def map[T <: Block : ClassTag](df: DataFrame, params: Parameters): DataFrame = {...}
    }
    
    class Pipeline(df: Dataframe) {
      ...
      def copy(newDf: DataFrame) = new Pipeline(newDf)
      ...
    
      def map[T <: Block : ClassTag](d: T#Parameters): Pipeline = {
        this.copy(registry.lookupRegistry[T].mapDf(df, d))
      }
      ...
    }
    
    case class NoSuchBlockException(declaredBlock: Class[_])
        extends Exception(s"No block registered $declaredBlock in current registry")
    
    class BlockRegistry {
      var registry: Map[ClassTag[_ <: Block], _ <: Block] = Map()
    
      def register[T <: Block : ClassTag](block: Block) = {
        registry += (classTag[T] -> block)
        this
      }
    
      def lookupRegistry[T <: Block : ClassTag]: Block = registry.get(classTag[T]) match {
        case Some(block) => block
        case _ => throw NoSuchBlockException(classTag[T].runtimeClass)
      }
    }
    
    object BlockRegistry {
      val registry: BlockRegistry = new BlockRegistry()
          .register[ParseURL](ParseURL())
          .register[CastColumn](CastColumn())
    }
    
    val pipeline = new Pipeline(df).map[ParseURL]("url", $"url")
    

    也许将块从特征替换为抽象类将帮助我传递一个隐式注册表并让块自己注册(在实例化时)。但是机制又太复杂了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-17
      • 1970-01-01
      • 2013-04-25
      相关资源
      最近更新 更多