Scala 的 Monocle 中的过滤列表

2024-01-06

给出以下代码:

case class Person(name :String)
case class Group(group :List[Person])

val personLens = GenLens[Person]
val groupLens = GenLens[Group]

我如何从选择中“过滤”掉某些人,不是通过索引,而是通过特定的属性Person, like:

val trav :Traversal[Group, Person] = (groupLens(_.group) composeTraversal filterWith((x :Person) => /*expression of type Boolean here */))

我只找到了filterIndex函数,它只包含基于索引的列表中的元素,但这不是我想要的。

filterIndex接受以下类型的函数:(Int => Boolean)

而且我要:

filterWith(化名),需要一个(x => Boolean),其中 x 具有列表元素的类型,即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] =
  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.

好吧,更好了!


为什么还是不好

The “真正的”法律 https://hackage.haskell.org/package/lens-4.14/docs/Control-Lens-Traversal.html for Traversal没有在 Monocle 中编码TraversalLaws(至少不眼下 https://github.com/julien-truffaut/Monocle/pull/357),我们还希望这样的东西成立:

For any f: A => A and 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这可能会改变谓词的结果。

这就是为什么filterIndex是合法的——它的谓词将以下内容作为参数:modify无法触摸,所以你无法打破t.modify(f.compose(g)) === t.modify(f).compose(t.modify(g)) law.


故事的道德启示

你可以写一个非法的Traversal它会非法过滤东西并一直使用它,它很可能永远不会伤害你,而且没有人会认为你是一个可怕的人。所以,如果你愿意的话,就去吧。你可能永远不会看到filterWith在一个像样的镜头里library, 尽管。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Scala 的 Monocle 中的过滤列表 的相关文章

随机推荐