Scala单片眼镜中的过滤列表 [英] Filtering Lists in Scala's Monocle
问题描述
给出以下代码:
case class Person(name :String)
case class Group(group :List[Person])
val personLens = GenLens[Person]
val groupLens = GenLens[Group]
我该如何从选择中过滤"某些人,而不是通过索引,而不是通过Person
的特定属性,例如:
how can i "filter" out certain Persons from the selection, NOT by index but by a specific property of Person
, like:
val trav :Traversal[Group, Person] = (groupLens(_.group) composeTraversal filterWith((x :Person) => /*expression of type Boolean here */))
我只找到了filterIndex
函数,该函数仅包括基于索引的列表中的元素,但这不是我想要的.
I only found the filterIndex
function, which does only include elements from the list based on index, but this is not what i want.
filterIndex
具有类型为(Int => Boolean)
我想要:
filterWith
(组成名称),它取一个(x => Boolean)
,其中x为列表元素的类型,在此简短示例中为Person
.
filterWith
(made up name), that takes a (x => Boolean)
, where x has the type of the lists element, namely Person
in this short example.
这似乎非常实用和普遍,以至于我假设有人对此进行了思考,而我(我必须承认我对此事的理解有限)不明白为什么无法做到这一点.
This seems so practical and common that i assume somebody has thought about that and i (with my, i must admit limited understanding of the matter) don't see why it can't be done.
我是否缺少此功能,是尚未实现,还是出于某种原因而根本无法实现(请在时间允许的情况下进行解释).
Am i missing this functionality, is it not implemented yet or just plainly impossible for whatever reason (please do explain if you have the time).
谢谢.
推荐答案
错误的版本
我将从天真的尝试开始,像这样写.我在这里使用一个简单的列表版本,但是如果您愿意的话,您可能会更喜欢(使用Traverse
或其他工具).
import monocle.Traversal
import scalaz.Applicative, scalaz.std.list._, scalaz.syntax.traverse._
def filterWith[A](p: A => Boolean): Traversal[List[A], A] =
new Traversal[List[A], A] {
def modifyF[F[_]: Applicative](f: A => F[A])(s: List[A]): F[List[A]] =
s.filter(p).traverse(f)
}
然后:
import monocle.macros.GenLens
case class Person(name: String)
case class Group(group: List[Person])
val personLens = GenLens[Person]
val groupLens = GenLens[Group]
val aNames = groupLens(_.group).composeTraversal(filterWith(_.name.startsWith("A")))
val group = Group(List(Person("Al"), Person("Alice"), Person("Bob")))
最后:
scala> aNames.getAll(group)
res0: List[Person] = List(Person(Al), Person(Alice))
有效!
它有效,除了……
scala> import monocle.law.discipline.TraversalTests
import monocle.law.discipline.TraversalTests
scala> TraversalTests(filterWith[String](_.startsWith("A"))).all.check
+ Traversal.get what you set: OK, passed 100 tests.
+ Traversal.headOption: OK, passed 100 tests.
! Traversal.modify id = id: Falsified after 2 passed tests.
> Labels of failing property:
Expected List(崡) but got List()
> ARG_0: List(崡)
! Traversal.modifyF Id = Id: Falsified after 2 passed tests.
> Labels of failing property:
Expected List(ᜱ) but got List()
> ARG_0: List(ᜱ)
+ Traversal.set idempotent: OK, passed 100 tests.
五分之三不是很好.
让我们重新开始:
def filterWith2[A](p: A => Boolean): Traversal[List[A], A] =
new Traversal[List[A], A] {
def modifyF[F[_]: Applicative](f: A => F[A])(s: List[A]): F[List[A]] =
s.traverse {
case a if p(a) => f(a)
case a => Applicative[F].point(a)
}
}
val aNames2 = groupLens(_.group).composeTraversal(filterWith2(_.name.startsWith("A")))
然后:
scala> aNames2.getAll(group)
res1: List[Person] = List(Person(Al), Person(Alice))
scala> TraversalTests(filterWith2[String](_.startsWith("A"))).all.check
+ Traversal.get what you set: OK, passed 100 tests.
+ Traversal.headOption: OK, passed 100 tests.
+ Traversal.modify id = id: OK, passed 100 tests.
+ Traversal.modifyF Id = Id: OK, passed 100 tests.
+ Traversal.set idempotent: OK, passed 100 tests.
好的,更好!
Traversal
的真实"法律不是'在Monocle的TraversalLaws
中编码(目前至少不是 ),并且我们还想要类似的东西要保持的状态:
The "real" laws for Traversal
aren't encoded in Monocle's TraversalLaws
(at least not at the moment), and we additionally want something like this to hold:
对于任何
f: A => A
和g: A => A
,t.modify(f.compose(g))
应等于t.modify(f).compose(t.modify(g))
.
让我们尝试一下:
scala> val graduate: Person => Person = p => Person("Dr. " + p.name)
graduate: Person => Person = <function1>
scala> val kill: Person => Person = p => Person(p.name + ", deceased")
kill: Person => Person = <function1>
scala> aNames2.modify(kill.compose(graduate))(group)
res2: Group = Group(List(Person(Dr. Al, deceased), Person(Dr. Alice, deceased), Person(Bob)))
scala> aNames2.modify(kill).compose(aNames2.modify(graduate))(group)
res3: Group = Group(List(Person(Dr. Al), Person(Dr. Alice), Person(Bob)))
所以我们再没有运气了. filterWith
实际上合法的唯一方法是,如果我们保证绝不将其与modify
的参数一起使用,这可能会改变谓词的结果.
So we're out of luck again. The only way our filterWith
could actually be lawful is if we promise never to use it with an argument to modify
that might change the result of the predicate.
这就是为什么filterIndex
合法的原因-它的谓词将modify
不能触及的东西作为参数,因此您不能违反t.modify(f.compose(g)) === t.modify(f).compose(t.modify(g))
律.
This is why filterIndex
is legit—its predicate takes as an argument something that modify
can't touch, so you can't break the t.modify(f.compose(g)) === t.modify(f).compose(t.modify(g))
law.
您可以编写一个非法的Traversal
来进行非法的过滤工作并一直使用,很可能它永远不会伤害您,而且没人会认为您是一个可怕的人.因此,如果需要的话,就去吧.不过,您可能永远不会在像样的镜头库中看到filterWith
.
You could write an unlawful Traversal
that does unlawful filtering stuff and use it all the time and it's pretty likely that it will never hurt you and that nobody will ever think you are a horrible person. So go for it, if you want. You'll probably never see a filterWith
in a decent lens library, though.
这篇关于Scala单片眼镜中的过滤列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!