在Scalaz中编写Monoid for Filter返回Disjunction的更好方法

时间:2015-12-11 00:22:36

标签: scala scalaz

我正在尝试为Filter实现一个monoid。 (它与 Scalaz )中的Reader[A, Boolean]相同

type Filter[A] = A => Boolean 

实现过滤器的最简单方法是

implicit def monoidFilter[A] = new Monoid[Filter[A]] {
  override def zero: Filter[A] =
    a => false
  override def append(f1: Filter[A], f2: => Filter[A]): Filter[A] =
    a => f1(a) || f2(a)
}

// test
case class User(name: String, city: String)
val users = List(User("Kelly", ".LONDON"), User("John", ".NY"), User("Cark", ".KAW"))

// filtered: List(User(Kelly,.LONDON), User(John,.NY))
(users filter (london |+| ny) size) shouldBe 2

我发现这是Disjunction。现在我们mappend可以monoidFilter

import Tags._
import syntax.tag._

val london = (u: User) => Disjunction(u.city endsWith(".LONDON"))
val ny     = (u: User) => Disjunction(u.city endsWith("NY"))

(users filter { u => (london |+| ny)(u).unwrap }).size shouldBe 2

但是代码在可用性方面变得更长。

所以我的问题是,有更好的方法来实施monoidFilter?我虽然它已经在 Scalaz 中实现但我还没找到。

2 个答案:

答案 0 :(得分:4)

Monoid[A => Boolean]

你实际上可以使用原始别名

type Filter[A] = A => Boolean

如果你想特别or monoid,你很容易构建这样的实例

import scalaz.std.anyVal._
import scalaz.std.function._

implicit def boolMonoid[A] = function1Monoid[A, Boolean](booleanInstance.disjunction)

这将显着简化语法

val london: Filter[User] = _.city endsWith ".LONDON"
val ny: Filter[User] = _.city endsWith "NY"

users filter( london |+| ny)

Rig[A => Boolean]

但如果我是你,我会使用Rig中的semiringspire library)代替Monoid

我不知道是否有可以将BooleanRig升级到Function1 monad的库,但是手动编写它很容易:

import spire.algebra.Rig

implicit def filterRig[A] = new Rig[Filter[A]] {
  def plus(x: Filter[A], y: Filter[A]): Filter[A] = v => x(v) || y(v)
  def one: Filter[A] = Function.const(true)
  def times(x: Filter[A], y: Filter[A]): Filter[A] = v => x(v) && y(v)
  def zero: Filter[A] = Function.const(false)
} 

甚至更一般的版本

import spire.std.boolean._

implicit def applicativeRigU[MX, X](implicit G: Unapply.AuxA[Applicative, MX, X], rig: Rig[X]): Rig[MX] = {
  val A: Applicative[G.M] = G.TC
  val L: G.M[X] === MX = Leibniz.symm[Nothing, Any, MX, G.M[X]](G.leibniz)
  val rigA = new Rig[G.M[X]] {
    def plus(x: G.M[X], y: G.M[X]): G.M[X] = A.lift2(rig.plus)(x, y)
    def one: G.M[X] = A.point(rig.one)
    def times(x: G.M[X], y: G.M[X]): G.M[X] = A.lift2(rig.times)(x, y)
    def zero: G.M[X] = A.point(rig.zero)
  }

  L.subst(rigA)
}

现在您可以添加其他过滤器,例如

val nameJ: Filter[User] = _.name startsWith "J"

然后运行

import spire.syntax.rig._

users filter (london + ny * nameJ)

答案 1 :(得分:2)

我不确定我是否理解您的问题,但Disjunction中的\/scalaz与scala lib中的Either类似,但有点不同。使用Disjunction,您可以在同一时间为不同类型的值建模,例如:

val file = \/.fromTryCatchNonFatal( scala.io.Source.fromFile("file.txt") )

如果file不存在,BufferedSource可以是Throwablefile.txt类型。

file: scalaz.\/[Throwable,scala.io.BufferedSource] = -\/(java.io.FileNotFoundException: file.txt (No such file or directory))

现在您可以映射和过滤此值。 在你的情况下,monoidFilter是建模布尔谓词的好方法。 我希望你有意义。