我对此问题有类似的问题(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是否应该激励我。答案 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)
我不认为您可以在引用的问题中轻松应用解决方案,因为您的类型T
和U
之间存在依赖关系,并且方向不好:{{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)。但这种机制再次过于复杂。