Ton*_* K. 5 functional-programming scala composition scalaz
我有一个列表,我希望过滤结果.
用户可以为行上的任何属性提供特定限制(例如,我只想查看x == 1的行).如果它们没有指定限制,那么当然不使用谓词.当然,最简单的形式是:
list.filter(_.x == 1)
Run Code Online (Sandbox Code Playgroud)
有许多可能的简单谓词,我正在构建一个新的谓词函数,其代码将用户搜索项(例如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)))
}
Run Code Online (Sandbox Code Playgroud)
这非常有效.运行时,它会正确过滤,调试输出证明调用最少数量的函数来适当地过滤列表:
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))
Run Code Online (Sandbox Code Playgroud)
我真的很高兴这出来了.
但是,我不禁想到有一种更实用的方法(例如Monoids和Functors以及广义总和)......但我无法弄清楚如何使其工作.
我尝试了一个scalaz示例,表明我需要创建一个隐式零和半群,但我无法得到Zero [ResultRow => Boolean]来进行类型检查.
您可以使用以下方法稍微简化代码(无需迁移到 Scalaz)forall:
def makePredicate(a: Option[Int], b: Option[Int]): ResultRow => Boolean = {\n val expr = List(\n a.map(i => xMatches(_: ResultRow, i)),\n b.map(i => yMatches(_: ResultRow, i))\n ).flatten\n\n (r: ResultRow) => expr.forall(_(r))\n}\nRun Code Online (Sandbox Code Playgroud)\n\nSome(Identity)请注意,这也消除了包含在列表中的需要。
如果您有很多行,我建议使用将函数与用户输入相zip匹配,如下所示:xMatches
val expr = List(a, b) zip List(xMatches, yMatches) flatMap {\n case (maybePred, matcher) => maybePred.map(i => matcher(_: ResultRow, i))\n}\nRun Code Online (Sandbox Code Playgroud)\n\n两行实际上并没有更简洁或可读,但四行或五行则更简洁或可读。
\n\n要回答关于 Scalaz 的问题,问题是 存在两种可能的幺半群Boolean,而 Scalaz 不会为你选择一个\xe2\x80\x94,相反,你必须用 Haskell 的newtype包装器之类的东西来标记你的布尔值,以指示你选择哪个幺半群想要使用(在 Scalaz 7\xe2\x80\x94in 6 中,方法有点不同)。
一旦您指定了要使用的幺半群Boolean,则幺半群 forFunction1将启动,并且无需执行任何操作\xe2\x80\x94,您无需显式定义零Identity。例如:
import scalaz._, Scalaz._\n\ndef makePredicate(a: Option[Int], b: Option[Int]): ResultRow => Boolean =\n List(a, b).zip(List(xMatches, yMatches)).flatMap {\n case (maybePred, matcher) =>\n maybePred.map(i => matcher(_: ResultRow, i).conjunction)\n }.suml\nRun Code Online (Sandbox Code Playgroud)\n\n这里我们刚刚计算了函数的总和ResultRow => Boolean @@ Conjunction。