在Scala中,如何推断def的一个类型参数?

时间:2017-12-14 16:39:42

标签: scala

我对此问题有类似的问题(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是否应该激励我。

3 个答案:

答案 0 :(得分: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]

答案 1 :(得分:1)

我不认为您可以在引用的问题中轻松应用解决方案,因为您的类型TU之间存在依赖关系,并且方向不好:{{1} }取决于T,您想省略U

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

BlockFactory

所以你可以用它作为

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

通过这种方式,您不必指定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来调用其()。这样对我来说感觉不太自然,但你节省了一些打字。

答案 2 :(得分:0)

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

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

将块从trait替换为抽象类可能会帮助我传递一个隐式注册表并让块自己注册(在instanciation)。但这种机制再次过于复杂。