In Arrow Optics, how can I create a lens that only...
# arrow
d
In Arrow Optics, how can I create a lens that only focuses of some of the elements in a list that fulfil a condition? Say the following example:
Copy code
@optics data class Person(val name: String, val age: Int, val friends: List<Person>) {
  companion object
}
Copy code
Person.friends.every(Every.list()).age.modify(this) { it + 1 }
How can i add a filter on some property, say name starts with
x
and then modify age with the lens that focus those elements that fulfil the predicate?
d
I think you can make your own Every.list() that takes a lambda...?
Or just use an
if (...) ... else it
inside the modify
d
Inside the modify, i'm focusing the lens on the property
age
so I won't have the other properties of the item I'm looking at right?
d
Yeah, true, I didn't notice you were focusing on something else there...
d
I think you can make your own Every.list() that takes a lambda...?
Tried to find this but couldn't 😞 I know I've seen a sample of filtering based on
index
but not properties 😞
d
Every.list() is just:
Copy code
public fun <A> list(): Every<List<A>, A> =
      object : Every<List<A>, A> {
        override fun modify(source: List<A>, map: (focus: A) -> A): List<A> =
          source.map(map)

        override fun <R> foldMap(M: Monoid<R>, source: List<A>, map: (focus: A) -> R): R =
          source.foldMap(M, map)
      }
I think you could add a parameter there that could take a filter and use filter before map or foldMap...?
index will help you to focus on ONE entry, so it might not be what you're looking for here, but I did make one using a lambda once...
d
Yeah I've seen findOrNull, but in my case I'd like to find multiple.
d
This might work:
Copy code
import arrow.core.foldMap
import arrow.optics.Every
import arrow.typeclasses.Monoid

val list = listOf("one", "two", "two", "three")

fun <A> listFiltered(block: (A) -> Boolean) = object : Every<List<A>, A> {
    override fun modify(source: List<A>, map: (focus: A) -> A): List<A> =
        source.filter(block).map(map)

    override fun <R> foldMap(M: Monoid<R>, source: List<A>, map: (focus: A) -> R): R =
        source.filter(block).foldMap(M, map)
}

println(listFiltered<String> { it == "two" }.modify(list) { "three" })
Nope, it doesn't... it just returns the modified entries... just a second...
This could do it, although I'm not sure about foldMap...:
Copy code
fun <A> listFiltered(block: (A) -> Boolean) = object : Every<List<A>, A> {
    override fun modify(source: List<A>, map: (focus: A) -> A): List<A> =
        source.map { if (block(it)) map(it) else it }

    override fun <R> foldMap(M: Monoid<R>, source: List<A>, map: (focus: A) -> R): R =
        source.filter(block).foldMap(M, map)
}
@David
Because in foldMap it transforms the result to another type, so you can't just return the old value...
I'm not sure where it's actually used though...
Maybe you don't understand how to use it?
Copy code
Person.friends.every(listFiltered { it.name ==... }).age.modify(this) { it + 1 }
a
In general filtering is not a lens, since it doesn’t allow you to set values back
1
d
since it doesn’t allow you to set values back
What do you mean?
Is there any better way to do this then @Alejandro Serrano.Mena?
I've had the same need in the past...
We're still trying to pinpoint on certain values in an immutable structure and modify them... just not all of them.
a
The problem is that in order to define a two-way optic, you need to define how to get and how to set elements; but it’s not clear how to do it with filtering
For example, what if you have a list of numbers, filter to get only the even ones, and then you apply { it + 1 } ?
d
That's what I wrote above... no? You mean with that optic I wrote, you can't get the whole set, just the filtered one?
a
Yeah, but this would not satisfy the optics laws, so things may behave unexpectedly
d
I was away in a meeting. Okay, so this is not really supported then as I understand it? Thanks for then answer @Alejandro Serrano.Mena 🙏