在Scala的Monocle中过滤列表
给出以下代码:
case class Person(name :String) case class Group(group :List[Person]) val personLens = GenLens[Person] val groupLens = GenLens[Group] 我怎样才能从选择中“过滤掉”某些人,不是通过索引而是通过Person的特定属性,例如: val trav :Traversal[Group,Person] = (groupLens(_.group) composeTraversal filterWith((x :Person) => /*expression of type Boolean here */)) 我只找到了filterIndex函数,它只包含基于索引的列表中的元素,但这不是我想要的. filterIndex采用以下类型的函数:(Int => Boolean) 而且我要: filterWith(由名称组成),它采用(x => Boolean),其中x具有lists元素的类型,即此简短示例中的Person. 这似乎是如此实际和普遍,以至于我认为有人已经考虑过这一点而且我(我必须承认对此事的理解有限)不明白为什么不能这样做. 我是否遗漏了这个功能,它是否尚未实现,或者由于某种原因显然是不可能的(如果你有时间,请解释). 谢谢. 解决方法
一个糟糕的版本
我会从天真的尝试开始写这样的东西.我在这里使用一个简单的列表版本,但如果你愿意,你可以得到更好的(使用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] { 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.modify id = id: OK,passed 100 tests. + Traversal.modifyF Id = Id: OK,passed 100 tests. + Traversal.set idempotent: OK,passed 100 tests. 好的,更好! 为什么它仍然很糟糕 Traversal的“real” laws不是用Monocle的TraversalLaws编码的(至少不是at the moment),我们还想要这样的东西:
我们来试试吧: 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,Person(Bob))) scala> aNames2.modify(kill).compose(aNames2.modify(graduate))(group) res3: Group = Group(List(Person(Dr. Al),Person(Dr. Alice),Person(Bob))) 所以我们再次失去运气.我们的filterWith实际上是合法的唯一方法是,如果我们保证永远不会使用它来修改可能会改变谓词结果的参数. 这就是为什么filterIndex是合法的 – 它的谓词作为一个参数,修改不能触及,所以你不能破坏t.modify(f.compose(g))=== t.modify(f).compose (t.modify(g))法律. 故事的道德启示 你可以编写一个非法的Traversal来做非法过滤的东西,并且一直使用它,它很可能永远不会伤害你,并且没有人会认为你是一个可怕的人.如果你愿意,那就去吧.但是,你可能永远不会在一个像样的镜头库中看到过滤器. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |