【问题标题】:Rewrite scala code to be more functional重写 scala 代码以使其更具功能性
【发布时间】:2018-05-23 16:49:50
【问题描述】:

我正在尝试自学 Scala,同时尝试编写函数式语言的惯用代码,即编写更好、更优雅的函数式代码。

我有以下代码可以正常工作:

import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SparkSession}
import java.time.LocalDate
object DataFrameExtensions_ {
  implicit class DataFrameExtensions(df: DataFrame){
    def featuresGroup1(groupBy: Seq[String], asAt: LocalDate): DataFrame = {df}
    def featuresGroup2(groupBy: Seq[String], asAt: LocalDate): DataFrame = {df}
  }
}
import DataFrameExtensions_._
val spark = SparkSession.builder().config(new SparkConf().setMaster("local[*]")).enableHiveSupport().getOrCreate()
import spark.implicits._
val df = Seq((8, "bat"),(64, "mouse"),(-27, "horse")).toDF("number", "word")
val groupBy = Seq("a","b")
val asAt = LocalDate.now()
val dataFrames = Seq(df.featuresGroup1(groupBy, asAt),df.featuresGroup2(groupBy, asAt))

最后一行让我很困扰。这两个函数(featuresGroup1featuresGroup2)都具有相同的签名:

scala> :type df.featuresGroup1(_,_)
(Seq[String], java.time.LocalDate) => org.apache.spark.sql.DataFrame

scala> :type df.featuresGroup2(_,_)
(Seq[String], java.time.LocalDate) => org.apache.spark.sql.DataFrame

并采用相同的vals 作为参数,所以我假设我可以以更实用的方式编写该行(可能以某种方式使用.map),这意味着我可以只编写一次参数列表并将其传递给两个函数.我无法弄清楚语法。我想也许我可以构建一个这些函数的列表,但这不起作用:

scala> Seq(featuresGroup1, featuresGroup2)
<console>:23: error: not found: value featuresGroup1
       Seq(featuresGroup1, featuresGroup2)
           ^
<console>:23: error: not found: value featuresGroup2
       Seq(featuresGroup1, featuresGroup2)
                           ^

谁能帮忙?

【问题讨论】:

    标签: scala


    【解决方案1】:

    我想也许我可以构建一个这些函数的列表,但这不起作用:

    既然上面已经有了正确的语法df.featuresGroup1(_,_),为什么还要在这里写featuresGroup1/2

    Seq(df.featuresGroup1(_,_), df.featuresGroup2(_,_)).map(_(groupBy, asAt))
    

    df.featuresGroup1 _ 应该也可以。

    df.featuresGroup1 如果你有一个预期的类型,它本身就可以工作,例如

    val dataframes: Seq[(Seq[String], LocalDate) => DataFrame] = 
      Seq(df.featuresGroup1, df.featuresGroup2)
    

    但在这种特定情况下,提供预期类型比使用 lambda 更详细。

    【讨论】:

    • 既然上面已经有了正确的语法 df.featuresGroup1(_,_),为什么还要在这里写 featuresGroup1/2? 可能是因为我还在学习
    • 您的建议似乎有效,并且最接近我设想的纯功能方法。让我检查一下,然后我会接受你的回答。谢谢你:)
    • P.S.你的回答有一个小错别字。 asAt,不是asAdt
    • 在这种特定情况下,提供预期的类型比使用 lambdas 更冗长同意。我更喜欢 lambda 方法提供的类型推断
    • 修正了错字。
    【解决方案2】:

    我想也许我可以构建一个这些函数的列表,但那不起作用

    您需要通过使用下划线运算符显式执行 eta expansion 以将方法转换为函数(它们在 Scala 中不一样):

    val funcs = Seq(featuresGroup1 _, featuresGroup2 _)
    

    或使用占位符:

    val funcs = Seq(featuresGroup1(_, _), featuresGroup2(_, _))
    

    您使用map 运算符是绝对正确的:

    val dataFrames = funcs.map(f => f(groupBy, asAdt))
    

    我强烈建议不要使用 StringSeq 类型的隐式,因为如果在多个地方使用,这些会导致在代码中不会立即明显的细微错误,并且代码在移动时很容易中断某处。

    如果您想使用隐式,请将它们包装成自定义类型:

    case class DfGrouping(groupBy: Seq[String]) extends AnyVal
    
    implicit val grouping: DfGrouping = DfGrouping(Seq("a", "b"))
    

    【讨论】:

    • 谢谢奥列格。我试过val funcs = Seq(featuresGroup1 _, featuresGroup2 _),但失败了:&lt;console&gt;:24: error: not found: value featuresGroup1。这可能是因为这些函数是在隐式类中定义的吗?
    • Seq(df.featuresGroup1(_,_), df.featuresGroup2(_,_)).map(_(groupBy, asAt)) 成功了
    • 感谢您不使用隐式的建议。我承认,当我看到@vindev 的建议时,我有点不安,因为我想知道“如果我有多个隐式值怎么办”,并且确实认为可能会出现细微的错误。谢谢。
    【解决方案3】:

    为什么不直接在DataFrameExtensions 中创建一个函数来这样做呢?

    def getDataframeGroups(groupBy: Seq[String], asAt: String) = Seq(featuresGroup1(groupBy,asAt), featuresGroup2(groupBy,asAt))
    

    【讨论】:

    • 谢谢@RoberMP。 Thx,是的,这行得通。虽然我不会接受你的回答(我保证稍后会),因为我有兴趣阅读可能提出的任何其他建议,我特别想知道这是否可以通过.map() 来完成而不是使用 helper 函数(这是我对您的解决方案的看法)。
    【解决方案4】:

    我认为您可以创建如下函数列表:

    val funcs:List[DataFrame=>(Seq[String], java.time.LocalDate) => org.apache.spark.sql.DataFrame]  = List(_.featuresGroup1, _.featuresGroup1)
    funcs.map(x => x(df)(groupBy, asAt))
    

    您似乎有一个将DataFrame 转换为另一个DataFrame 的函数列表。如果这是模式,您可以在 Scalaz 中使用 Endo 更进一步

    【讨论】:

    • 谢谢宾子。我试过:val funcs:List[DataFrame=&gt;(Seq[String], java.time.LocalDate) =&gt; org.apache.spark.sql.DataFrame] = List(_.featuresGroup1, _.featuresGroup1) 失败了 &lt;console&gt;:24: error: type mismatch;|found : org.apache.spark.sql.DataFrame|(which expands to) org.apache.spark.sql.Dataset[org.apache.spark.sql.Row]|required: (Seq[String], java.time.LocalDate) =&gt; org.apache.spark.sql.DataFrame|(which expands to) (Seq[String], java.time.LocalDate) =&gt; org.apache.spark.sql.Dataset[org.apache.spark.sql.Row]
    • 从我这里的函数中运行良好:List[org.apache.spark.sql.DataFrame => ((Seq[String], java.time.LocalDate) => org.apache.spark. sql.DataFrame)] = List(, ) res1: List[org.apache.spark.sql.DataFrame] = List([number: int, word: string], [number: int, word:字符串])
    • 试过了,得到了&lt;console&gt;:1: error: ';' expected but '=' found. :( 。对不起,我确定我只是一个愚蠢的新手,但我就是不知道问题出在哪里。
    • 对不起,我的回复不是代码,是答案中代码行的返回结果。我在问题中使用了您的示例代码。它适用于我的回答。也许你的 REPL 里有东西
    • 如果您复制代码和错误的屏幕截图,我可能会帮助找出它。这实际上是最重要的纯函数概念之一,currieduncurried。列表中的数据类型是柯里化函数。 Scala 支持柯里化函数。我建议您对该错误进行更多调查。柯里化函数是 FP 中一个非常重要的概念。在 FP 中,一切都可以作为函数。
    【解决方案5】:

    我最喜欢这个答案,由 Alexey Romanov 提供。

    import org.apache.spark.SparkConf
    import org.apache.spark.sql.{DataFrame, SparkSession}
    import java.time.LocalDate
    object DataFrameExtensions_ {
      implicit class DataFrameExtensions(df: DataFrame){
        def featuresGroup1(groupBy: Seq[String], asAt: LocalDate): DataFrame = {df}
        def featuresGroup2(groupBy: Seq[String], asAt: LocalDate): DataFrame = {df}
      }
    }
    import DataFrameExtensions_._
    val spark = SparkSession.builder().config(new SparkConf().setMaster("local[*]")).enableHiveSupport().getOrCreate()
    import spark.implicits._
    val df = Seq((8, "bat"),(64, "mouse"),(-27, "horse")).toDF("number", "word")
    val groupBy = Seq("a","b")
    val asAt = LocalDate.now()
    Seq(df.featuresGroup1(_,_), df.featuresGroup2(_,_)).map(_(groupBy, asAt))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      • 1970-01-01
      • 2021-06-14
      • 1970-01-01
      相关资源
      最近更新 更多