Hi! I have question about `optics` and `Traversal...
# arrow
g
Hi! I have question about
optics
and
Traversals
My data classes:
Copy code
@optics
data class Foo(
    val bars: List<Bar>,
) {
    companion object
}

@optics
data class Bar(
    val id: Int,
    val flag: Boolean
) {
    companion object
}
I want to copy-modify
Foo
instance with change only a few
Bar
in list I can change only one element by index
Copy code
val index = foo.bars.indexOfFirst { it.id == 0 }
val newFoo = Foo.bars[index].flag.set(foo, false)
Also I can change every element used
Every
Copy code
val newFoo = Foo.bars.every(Every.list()).flag.set(foo, false)
Is there any way to add a predicate to
Every
? Like this
Copy code
val newFoo = Foo.bars.every(Every.list())
  .where { it.id > 100 }
  .flag.set(foo, false)
a
unfortunately using a filter doesn't give raise to a well-behaved optic (what should happen if the
set
changes the information the predicate is about?) my suggestion in those cases is to use
modify
:
Copy code
val newFoo = Foo.bars.every(Every.list()).modify {
  if (it.id > 100) Bar.flag.set(it, false) else it
}
(or actually, I would write it as)
Copy code
val newFoo = Foo.bars.every(Every.list()).modify {
  if (it.id > 100) it.copy(flag = false) else it
}
g
Thanks!
d
@Alejandro Serrano.Mena Isn't there a concept that can be made for this use case? This comes up pretty often, and although a Lens isn't the right abstraction, maybe something else could be done for this...?
a
there's actually a notion of "read-only lens" that we have in Arrow 1.x, but are dropping in 2.0 because it made the whole thing more complicated and the error messages worse. But this won't help in this case, because what you want is to
set
The core of the problem here is to ensure that the setting does not affect the filtering... which I don't know how to do, honestly. One possibility is to have a set of "delicate optics" like
filter
which can be used in some scenarios, but not in others. I'll try to write some code this afternoon...
d
Or maybe leave the read-only lens as a separate construct used to convert a Lens when filtered to that read-only lens (the regular Lens wouldn't inherit it...)?
a
yeah, that was the old design, but it made the hierarchy much more complicated (you can read about it in https://arrow-kt.io/learn/immutable-data/intro/#many-optics-to-rule-them-all) still, the problem here is another one: it's OK to use filter, if your filtering and setting are "disjoint". This is why I think having a set of "potentially unsafe" operations could be more useful than simply restricting them
s
This is why I think having a set of "potentially unsafe" operations could be more useful than simply restricting them
I agree, in KotlinX Serialization JsonPath I also have an unsafe
Prism
to
parse
, https://github.com/nomisRev/kotlinx-serialization-jsonpath/blob/9d3125957cf45958e7[…]f07de98200d8/src/commonMain/kotlin/io/github/nomisrev/optics.kt. It's too useful to not have.
d
if your filtering and setting are "disjoint"
What do you mean? Isn't filtering just pinpointing a set of elements to
set()
in just like a Lens points at one? I always had trouble grasping the problem, sorry for the question 🐢...
a
what I mean by disjoint here is that the filtering is made on a different set of fields than the modification is applied to an example of the problematic case is the following
Copy code
val list: List<Int> = ...
list.filter { it.isEven() }.modify { it + 1 }
what should happen here? I am filtering even numbers, but then I modify and make them odd; what should be the behavior here?
d
You're saying that the filter is supposed to point to even numbers and it lost that in the modify block...? If the user uses the terminal function modify { } should that really matter? As long as they're not going to keep on digging in further through the lens after modifying...
set { } wouldn't allow using every or any other composition of lenses after it.
a
the problem is not modify or set itself, but the fact that you keep digging and then modify
d
But while you're digging, it'll never become odd numbers... only at the end when you modify the list... so then what's the problem you were raising before?
a
d
@Alejandro Serrano.Mena I'm not sure why your example is the wrong result:
Copy code
* p.modify(p.modify(n) { it + 1 }) { it + 1 }
 * //       ---------------------- = 3
 * // ---------------------------------------- = null
in the end we get null, which IS filtered... what were you expecting?
a
It’s assumed from the theory of optics that “modify(f) compose modify(g)” is equivalent to “modify(f compose g)”, which does not hold in this case
d
Apart from the theory, when would that practically break something?
a
This is exactly what makes this optic harder to use, as the whole building of optics assume a few invariants in their usage
That’s a tough question, because it depends of what you consider “practical”. Most people use that optic either for getting only (which is always ok) or filter about one thing to change something else , so their uses are fine This is why I preferred to name them “delicate” instead of “unlawful” or “dangerous”, because actually most people can use them correctly in an intuitive way
This article goes a bit deeper in why these laws are important http://oleg.fi/gists/posts/2018-12-12-find-correct-laws.html
d
I think that the example given in the KDocs should really give an example where use is Ok and where something actually breaks... otherwise, users that don't really know these laws will eventually use this in the wrong way... I don't think all typical users will go through all that or really have enough of a function/mathematical background (we all learn mathematics, but not everyone uses it so often to remember everything...). And this feature is really useful when trying to alter a very deep nested class/map/list hierarchy -- only on certain elements in the list. Currently the alternative isn't too clean...
a
Right now the KDoc mentions that using it for getting is OK and that if you don’t modify the field you filter on, that’s also ok. Do you think that’s good enough?
d
should not alter whether the predicate holds on the value.
I don't know if most users of Arrow are familiar with these rules to know what could blow up if this rule is not kept... just stating that it's a "RULE" might not be enough to prevent users from shooting themselves in the foot... a clear example of where this is wrong would be much better... but I guess that's hard to find?
I myself don't see how this would blow up in my use cases...
a
I can make it even a bit clearer, but the main point here is that using filter and modify you may come to weird situations (like the double modify) where a rule from the rest of optics does not hold. But it’s one of those things where a small example feels too small and contrived, and a big one too big
d
Yeah, that's the problem with kdocs... 🤔. But maybe having a link to the docs or a tutorial for those that don't really see when this would go wrong (including me... 😵‍💫), and when it IS right to use this?