我的结果列在我希望过滤的列表中。
用户可以为行中的任何属性提供特定限制(例如,我只想查看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示例,表明我需要创建一个隐含的零和半群,但我无法得到零[ResultRow => Boolean]进行类型检查。
答案 0 :(得分:4)
您可以使用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)
的需要。
如果你有很多行,我建议使用zip
将xMatches
函数与用户输入相匹配,如下所示:
val expr = List(a, b) zip List(xMatches, yMatches) flatMap {
case (maybePred, matcher) => maybePred.map(i => matcher(_: ResultRow, i))
}
对于两行来说,它不是更简洁或可读,而是有四行或五行。
要回答关于Scalaz的问题,问题是Boolean
有两个可能的幺半群,Scalaz不为你选择一个 - 而是你必须用Haskell的{{{ 1}}包装器指示你想要使用哪个monoid(在Scalaz 7-in 6中,方法略有不同)。
一旦你为newtype
指出了你想要的幺半群,Boolean
的幺半群就会开始,没有什么可做的了 - 你不需要定义Function1
}显式为零。例如:
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
函数的总和。
答案 1 :(得分:3)
我非常喜欢的一个简化是使用Function1 [A,Boolean]的库pimp来简化那种谓词管道,它将标准布尔表达式提升到谓词。这是我的一个子集:
implicit def toRichPredicate[A](f: Function1[A, Boolean]) = new RichPredicate(f)
def tautology[A] = (x:A)=>true
def falsehood[A] = (x:A)=>false
class RichPredicate[A](f: Function1[A, Boolean]) extends Function1[A, Boolean] {
def apply(v: A) = f(v)
def &&(g: Function1[A, Boolean]): Function1[A, Boolean] = {
(x: A) => f(x) && g(x)
}
def ||(g: Function1[A, Boolean]): Function1[A, Boolean] = {
(x: A) => f(x) || g(x)
}
def unary_! : Function1[A, Boolean] = {
(x: A) => !f(x)
}
}
我发现这可以重复使用。有了类似的东西,你的减少就会变成
list.flatten.foldLeft(tautology)(&&)
这很简单。它也指向了更深层次的功能性善良,因为谓词与重言式和&& amp;显然形成了一个幺半群,所以这一切都在Scalaz或Haskell中调用一些更高阶的类型-y善。在这两种情况下,它也会变得有点棘手,因为在其他情况下你可以使用monoid而不是由falsehood和||形成的谓词,因此需要重载实例解析。