糟糕的版本
我将从一个天真的尝试开始写这样的东西。我在这里使用一个简单的列表版本,但你可以得到更奇特的(与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, 尽管。