scala - 通过在Scala中组合Option [predicate]函数来构建最小谓词功能(可能与scalaz一起使用)

标签 scala functional-programming composition scalaz

我有一个想要过滤的列表中的结果。

用户可以为行中的任何属性提供特定的限制(例如,我只想查看x == 1的行)。如果没有指定限制,则当然不会使用该谓词。当然,最简单的形式是:

list.filter(_.x == 1)

有许多可能的简单谓词,而我正在使用将用户搜索项(例如Option [Int])转换为谓词函数或Identity(返回true的函数)的代码即时构建新的谓词函数。代码看起来像这样(为简洁起见,已缩短,添加了显式类型):
case class ResultRow(x: Int, y: Int)

object Main extends App {
  // Predicate functions for the specific attributes, along with debug output
  val xMatches = (r: ResultRow, i: Int) => { Console println "match x"; r.x == i }
  val yMatches = (r: ResultRow, i: Int) => { Console println "match y"; r.y == i }
  val Identity = (r : ResultRow) => { Console println "identity"; true }

  def makePredicate(a: Option[Int], b: Option[Int]) : ResultRow => Boolean = {
    // The Identity entry is just in case all the optional params are None 
    // (otherwise, flatten would cause reduce to puke)
    val expr = List(Some(Identity), 
                    a.map(i => xMatches(_: ResultRow, i)),
                    b.map(i => yMatches(_: ResultRow, i))
                   ).flatten

    // Reduce the function list into a single function. 
    // Identity only ever appears on the left...
    expr.reduceLeft((a, b) => (a, b) match {
      case (Identity, f) => f
      case (f, f2) => (r: ResultRow) => f(r) && f2(r)
    })
  }

  val rows = List(ResultRow(1, 2), ResultRow(3, 100))

  Console println rows.filter(makePredicate(Some(1), None))
  Console println rows.filter(makePredicate(None, None))
  Console println rows.filter(makePredicate(None, Some(100)))
  Console println rows.filter(makePredicate(Some(3), Some(100)))
}

这完美地工作。运行时,它会正确过滤,并且调试输出证明可以调用最少的函数来适当过滤列表:
match x
match x
List(ResultRow(1,2))
identity
identity
List(ResultRow(1,2), ResultRow(3,100))
match y
match y
List(ResultRow(3,100))
match x
match x
match y
List(ResultRow(3,100))

实际上,我对结果的发布感到非常满意。

但是,我忍不住想到了一种更实用的方法(例如Monoids和Functors以及广义和)...但是我不知道如何使它工作。

我尝试按照指示我需要创建一个隐式零和半组的scalaz示例进行操作,但是我无法获取Zero [ResultRow => Boolean]进行类型检查。

最佳答案

您可以使用forall方法来简化代码(无需移至Scalaz):

def makePredicate(a: Option[Int], b: Option[Int]): ResultRow => Boolean = {
  val expr = List(
    a.map(i => xMatches(_: ResultRow, i)),
    b.map(i => yMatches(_: ResultRow, i))
  ).flatten

  (r: ResultRow) => expr.forall(_(r))
}

注意,这也消除了在列表中包括Some(Identity)的需要。

如果您有很多行,建议您使用zipxMatches函数与用户输入进行匹配,如下所示:
val expr = List(a, b) zip List(xMatches, yMatches) flatMap {
  case (maybePred, matcher) => maybePred.map(i => matcher(_: ResultRow, i))
}

两行实际上并没有更加简洁明了,而是四到五行。

为了回答有关Scalaz的问题,问题是Boolean有两个可能的monoid,而Scalaz却没有为您选择一个-而是您必须用Haskell的newtype包装器之类的 bool 值来标记 bool 值,以指示您要选择哪个monoid使用(在Scalaz 7中-在6中,方法有所不同)。

指出要使用Boolean的monoid后,将启动Function1的monoid,无需执行任何操作-您无需显式定义Identity零。例如:
import scalaz._, Scalaz._

def makePredicate(a: Option[Int], b: Option[Int]): ResultRow => Boolean =
  List(a, b).zip(List(xMatches, yMatches)).flatMap {
    case (maybePred, matcher) =>
      maybePred.map(i => matcher(_: ResultRow, i).conjunction)
  }.suml

在这里,我们仅取了ResultRow => Boolean @@ Conjunction函数的总和。

关于scala - 通过在Scala中组合Option [predicate]函数来构建最小谓词功能(可能与scalaz一起使用),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14000520/

相关文章:

javascript - 继续使用 mergeMap 在 RxJs pipeable 中出错

typescript - 如何折叠两种不同的类型? FP-TS

c++ - 组合的实现——避免初始化列表

scala - 有没有办法通过命令行为 sbt 指定备用位置以查找构建配置?

scala - Spark : save and load machine learning model on s3

scala - 类型类解析中的隐式类别

java - 组合和聚合关系 UML 存在问题

javascript - 在 Angular 6 中的组件之间共享逻辑时如何使用组合而不是继承?

scala - 在对象背后拥有 trait 有什么好处?

java - 从 Eclipse 调试 Scala 库中的方法