Scala:一个模板,用于仅接受某个arity和某个输出的函数?

时间:2017-06-18 10:43:15

标签: scala apache-spark

我有一个类,其所有函数都具有相同的arity和相同类型的输出。 (为什么?每个函数都是一个单独的处理器,应用于Spark DataFrame并生成另一个DataFrame)。

所以,这个课程看起来像这样:

class Processors {
   def p1(df: DataFrame): DataFrame {...}
   def p2(df: DataFrame): DataFrame {...}
   def p3(df: DataFrame): DataFrame {...}
...
}

然后我通过映射Processors.getClass.getMethod来应用给定DataFrame的所有方法,这允许我添加更多处理器而不更改代码中的任何其他内容。

我想要做的是为Processors下的方法定义一个模板,该模板会限制所有方法只接受一个DataFrame并返回一个DataFrame。有没有办法做到这一点?

2 个答案:

答案 0 :(得分:1)

这是你的意思吗?

class Processors {
  type Template = DataFrame => DataFrame

  val p1: Template = ...
  val p2: Template = ...
  val p3: Template = ...

  def applyAll(df: DataFrame): DataFrame =
    p1(p2(p3(df)))
}

答案 1 :(得分:1)

对可以添加到"列表中的哪种功能实施限制"可以通过使用适当的容器类而不是泛型类来保存受限制的方法。然后,受限方法的容器可以是某个新类或对象的一部分或主程序的一部分。

通过使用容器(例如带有字符串键和限制值的Map)来保存特定类型的函数,您在下面失去的是编译时检查方法名称。例如叫三联vs trilpe

使用Function1 from the scala standard library可以将函数限制为采用类型T并返回相同类型T的type F[T]Function1[A,B]允许任何具有输入类型A和输出类型B的单参数函数,但我们希望这些输入/输出类型相同,因此:

type F[T] = Function1[T,T]

对于容器,我将证明scala.collection.mutable.ListMap[String,F[T]]假设以下要求:

  • 字符串名称引用函数(doThis,doThat,而不是1,2,3 ......)
  • 函数可以稍后添加到列表中(可变)

虽然您可以选择其他一些可变或不可变的集合类(例如Vector[F[T]],如果您只想对方法进行编号),并且仍然受益于未来开发人员可以将哪种函数包含到容器中。< / p>

抽象类型可以定义为:

type TaskMap[T] = ListMap[String, F[T]]

对于您的特定应用程序,您可以将其实例化为:

val Processors:TaskMap[Dataframe] = ListMap(
   "p1" -> ((df: DataFrame) => {...code for p1 goes here...}),
   "p2" -> ((df: DataFrame) => {...code for p2 goes here...}),
   "p3" -> ((df: DataFrame) => {...code for p3 goes here...})
)

然后调用您使用的其中一个函数

Processors("p2")(someDF)

为了简化演示,让我们暂时忘记Dataframes并考虑这个方案是否适用于整数。

考虑下面的简短程序。该系列&#34; myTasks&#34;只能包含从Int到Int的函数。下面的所有行都在scala解释器v2.11.6中进行了测试,因此您可以逐行跟踪。

import scala.collection.mutable.ListMap
type F[T] = Function1[T,T]
type TaskMap[T] = ListMap[String, F[T]]
val myTasks: TaskMap[Int] = ListMap(
    "negate" -> ((x:Int)=>(-x)),
    "triple" -> ((x:Int)=>(3*x))
)

我们可以在容器中添加一个新功能,添加7并命名为&#34; add7&#34;

myTasks += ( "add7" -> ((x:Int)=>(x+7)) )

并且scala解释器回复:

res0: myTasks.type = Map(add7 -> <function1>, negate -> <function1>, triple -> <function1>)

但是我们无法添加一个名为&#34; half&#34;因为它会返回一个Float,而Float不是一个Int,应该触发一个类型错误

myTasks += ( "half" -> ((x:Int)=>(0.5*x)) )

这里我们收到以下错误消息:

scala> myTasks += ( "half" -> ((x:Int)=>(0.5*x)) )
<console>:12: error: type mismatch;
 found   : Double
 required: Int
              myTasks += ( "half" -> ((x:Int)=>(0.5*x)) )
                                                   ^

在编译的应用程序中,这将在编译时找到。

如何调用以这种方式存储的函数对于单个调用来说有点冗长,但可以非常方便。

假设我们要打电话给&#34; triple&#34; 10月。

我们无法写

   triple(10)
   <console>:9: error: not found: value triple

相反它是

 myTasks("triple")(10)
 res4: Int = 30

如果您有一个要执行的任务列表但只想允许myTasks中列出的任务,则此表示法变得更有用。

假设我们想要在输入数据&#34; 10&#34;

上运行所有任务
 myTasks mapValues { _ apply 10 }
 res9: scala.collection.Map[String,Int] = 
    Map(add7 -> 17, negate -> -10, triple -> 30)

假设我们想要三倍,然后添加7,然后否定

如果单独需要每个结果,如上所述,则变为:

 List("triple","add7","negate") map myTasks.apply map { _ apply 10 } 
 res11: List[Int] = List(30, 17, -10)

但是&#34;三倍,然后加7,然后否定&#34;也可以描述一系列要做的步骤10,即我们想要 - ((3 * 10)+7)&#34;而斯卡拉也可以这样做

 val myProgram = List("triple","add7","negate") 
 myProgram map myTasks.apply reduceLeft { _ andThen _ }  apply 10 
 res12: Int = -37

打开为自己的可自定义任务编写解释器的大门,因为我们也可以编写

 val magic = myProgram map myTasks.apply reduceLeft { _ andThen _ } 

然后magic是一个函数,从int到int,它可以采用aribtrary整数或以函数的形式工作。

scala> magic(1)
res14: Int = -10

scala> magic(2)
res15: Int = -13

scala> magic(3)
res16: Int = -16

scala> List(10,20,30) map magic
res17: List[Int] = List(-37, -67, -97)