在 Scalaz 中为过滤器返回析取编写 Monoid 的更好方法
Better way to write Monoid for Filter returning Disjunction in Scalaz
我正在尝试为 Filter
实现一个幺半群。 (与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 中实现,但我还找不到。
我不确定我是否理解您的问题,但是 scalaz
中的 Disjunction
或 \/
类似于 scala lib 中的 Either
但有更多不同。使用 Disjunction
您可以同时为不同类型的值建模,例如:
val file = \/.fromTryCatchNonFatal( scala.io.Source.fromFile("file.txt") )
此处 file
可以是 BufferedSource
或 Throwable
类型,例如 file.txt
不存在时。
file: scalaz.\/[Throwable,scala.io.BufferedSource] = -\/(java.io.FileNotFoundException: file.txt (No such file or directory))
现在您可以映射和过滤该值。
在您的情况下, monoidFilter
是模拟布尔谓词的好方法。
我希望它对你有意义。
Monoid[A => Boolean]
你实际上可以保留原来的别名
type Filter[A] = A => Boolean
如果你特别想要or
幺半群,你很容易构造这样的实例
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
(semiring) from the spire 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)
我正在尝试为 Filter
实现一个幺半群。 (与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 中实现,但我还找不到。
我不确定我是否理解您的问题,但是 scalaz
中的 Disjunction
或 \/
类似于 scala lib 中的 Either
但有更多不同。使用 Disjunction
您可以同时为不同类型的值建模,例如:
val file = \/.fromTryCatchNonFatal( scala.io.Source.fromFile("file.txt") )
此处 file
可以是 BufferedSource
或 Throwable
类型,例如 file.txt
不存在时。
file: scalaz.\/[Throwable,scala.io.BufferedSource] = -\/(java.io.FileNotFoundException: file.txt (No such file or directory))
现在您可以映射和过滤该值。
在您的情况下, monoidFilter
是模拟布尔谓词的好方法。
我希望它对你有意义。
Monoid[A => Boolean]
你实际上可以保留原来的别名
type Filter[A] = A => Boolean
如果你特别想要or
幺半群,你很容易构造这样的实例
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
(semiring) from the spire 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)