【问题标题】:Functional way of conditional filtering and sorting in scalascala中条件过滤和排序的功能方式
【发布时间】:2012-10-25 22:47:59
【问题描述】:

我想对序列进行排序和/或过滤。基本上是这样的:

var result = getReallyLongSeq() // returns Seq[SomeClass]

if (doFilter) {
  result = result.filter( ... )
}

if (doSort) {
  result = result.sortWith( ... )
}

现在,这显然是一种有效的方法,但有没有更实用的方法呢?

【问题讨论】:

    标签: scala functional-programming


    【解决方案1】:

    它是否更具可读性还有待商榷。它的效率有点低,但它也纯粹是功能性的。这种方法也很容易扩展并且相当可维护。

    val f: Seq[SomeClass] => Seq[SomeClass] = if(doFilter) _.filter(...) else identity
    val s: Seq[SomeClass] => Seq[SomeClass] = if(doSort) _.sortWith(...) else identity
    (s compose f)(result)
    

    你也可以这样写,更像是OP中的代码。它也稍微更有效(但不太通用)。

    val filtered = if(doFilter) result.filter(...) else result
    if(doSort) filtered.sortWith(...) else filtered
    

    基于cmets的讨论

    如果出于某些原因您更喜欢在第一个示例中使用柯里化函数(如 cmets 中所述),您可以编写以下内容:

    def fc(df: Boolean)(xs: Seq[SomeClass]) = if(df) _.filter(...) else identity
    def sc(ds: Boolean)(xs: Seq[SomeClass]) = if(ds) _.sortWith(...) else identity
    (sc(doSort) compose fc(doFilter))(result)
    

    但是你可能会像这样进一步编写它并最终得到与第一个示例中给出的几乎相同的内容:

    def fc(df: Boolean)(xs: Seq[SomeClass]) = if(df) _.filter(...) else identity
    def sc(ds: Boolean)(xs: Seq[SomeClass]) = if(ds) _.sortWith(...) else identity
    val f = fc(doFilter)
    val s = sc(doSort)
    (s compose f)(result)
    

    【讨论】:

    • 实际上它并不是纯粹的函数式,因为f 和s 都考虑了外部状态,所以它们都会根据doFilter 和doSort 的值产生不同的结果。如果您希望它是纯函数式的,则必须为它们添加一个柯里化参数,说明是使用函数本身还是使用身份。
    • @drexin 这可以是纯粹的功能。这叫做闭包。如果doFilter 和doSort 是可变变量,那么不,这不起作用。但是,如果它们是不可变的并且仅依赖于全局常量或某些外部范围的不可变参数,那么这确实是一种函数式方法,您会发现它在 Haskell 等不允许不纯定义的语言中非常常用。跨度>
    • 好的,抱歉。这可能是,也可能不是纯粹的。但是从帖子本身来看,并不清楚 doFilter 和 doSort 来自哪里。只要是这种情况,我就会认为这可能是不纯的。
    • @drexin 看看我添加了什么。使函数柯里化实际上并没有改变代码的纯度。
    • 是的。例如,如果doSort 是var,则s 不会是引用透明的,sc 是。
    【解决方案2】:

    您可以使用scalaz oparator |> 或定义您自己的:

    class PipedObject[T](value: T)
    {
        def |>[R](f: T => R) = f(value)
    }
    
    implicit def toPiped[T](value: T) =  new PipedObject[T](value)
    
    (result |> (r => if (doFilter) r.filter(...) else r) 
            |> (r => if (doSort) r.sortWith(...) else r))
    

    【讨论】:

      【解决方案3】:

      如果没有库支持,您可以推出自己的Boolean Reader Monad。

      这是功能性的、纯粹的和可配置的。

      布尔阅读器

      case class BoolConf[A](run: Boolean => A) {
          def apply(b: Boolean) = run(b)
          def map[B](f: A => B): BoolConf[B] = BoolConf(b => f(run(b)))
          def flatMap[B](f: A => BoolConf[B]): BoolConf[B] = BoolConf(b => f(run(b))(b))
      }
      

      在这里,我们为 Boolean => A 做了一个包装器,它允许单子组合,并且它可能已经在某些库中实现,例如 scalaz。

      对于这种情况,我们只对run 方法感兴趣,但您可以对其他机会感兴趣。

      配置过滤器和排序

      然后我们用Reader包装我们的过滤器和排序检查

      val mFilter: Seq[SomeClass] => BoolConf[Seq[SomeClass]] = seq => BoolConf(if(_) seq.filter(...) else seq)
      val mSort: Seq[SomeClass] => BoolConf[Seq[SomeClass]] = seq => BoolConf(if(_) seq.sortWith(...) else seq)
      

      提升

      现在,组合这些函数,因为输出不再是简单的Seq,我们需要提升其中一个在BoolConf中工作

      def lift2Bool[A, B]: (A => B) => (BoolConf[A] => BoolConf[B]) =
          fun => cfg => BoolConf(bool => fun(cfg(bool)))
      

      现在我们可以将A => B 中的任何函数转换为BoolConf[A] => BoolConf[B] 中的提升函数

      作曲

      现在我们可以进行函数式组合了:

      val filterAndSort = lift2Bool(mSort) compose mFilter
      //or the equivalent
      val filterAndSort = mFilter andThen lift2Bool(mSort)
      //applies as in filterAndSort(<sequence>)(<do filter>)(<do sort>)
      

      还有更多

      我们还可以为 mFilter 和 mSort 创建一个通用的“构建器”

      val configFilter[SomeClass]: (SomeClass => Boolean) => Seq[MyClass] => BoolConf[Seq[SomeClass]] = 
          filterer => seq => BoolConf(if(_) seq.filter(filterer))
      

      您可以自己“排序”等价的排序

      感谢 Runar 的启发

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-10-07
        • 2015-08-29
        • 2020-06-19
        • 2020-04-19
        • 1970-01-01
        • 2021-11-14
        • 2018-03-16
        • 1970-01-01
        相关资源
        最近更新 更多