-
Notifications
You must be signed in to change notification settings - Fork 36
Question: Is it possible to do these two operations in "one go"?
Is it possible to do these two operations in "one go" (playground)?
const target = {
elements: [
{
time: 1,
subelements: [1,2,3,4]
},
{
time: 2,
subelements: [1,2,3,4]
},
{
time: 3,
subelements: [1,2,3,4]
}
]
}
const filteredByTime =
L.remove(['elements', L.filter(elem => elem.time < 2)],
target)
const filteredBySubelement =
L.remove(['elements', L.elems, 'subelements', L.filter(i => i < 3)],
filteredByTime)
filteredBySubelement
I mean filtering elements by time and subelements by their value. Without
actually calling L.remove
twice.
Answer: Yes it is.
Note: This is not directly about the question, but just a note that L.filter
is a little bit of a relic. It does have some uses, but often you actually want
to use L.elems
with L.when
.
Here is traversal to do both at once (playground):
L.remove(['elements',
L.elems,
L.choose(elem => elem.time < 2
? L.identity
: ['subelements', L.elems, L.when(i => i < 3)])],
target)
Note that traversals have this property that the focuses are non-overlapping.
You essentially cannot make a traversal where the focuses would overlap. This
is why the above uses L.choose
to decide what to do.
We can also write the above traversal using L.choice
in a more point-free
style (playground):
L.remove(['elements',
L.elems,
L.choice(L.when(elem => elem.time < 2),
['subelements', L.elems, L.when(i => i < 3)])],
target)
Here is a transform to do both (playground):
L.remove(['elements',
L.seq(L.filter(elem => elem.time < 2),
[L.elems, 'subelements', L.filter(i => i < 3)])],
target)
The transform version may appear simpler, because you can just sequence transforms one after another. However, generally speaking traversals are preferable to transforms, because you can only properly use transforms to modify things. IOW, you lose the bidirectionality. You can't fold over transforms in the same way as with traversals. (Well, there are ways to do that, but not as nicely as with traversals.) Traversals also have the nice property that they only go over the data structure once.
Note that in the traversal version you could also replace the traversal
L.elems, L.when(i => i < 3)
with L.filter(i => i < 3)
lens, but I recommend
the traversal version. You notice the difference if you try to fold over the
elements of the traversal.
Also, note that the traversal version goes over the elements
array just once.
The transform version goes over it twice.
BTW, I suspect that uses of L.identity
may be confusing. A way to think about
lenses and traversals is that they declare a path or set of paths (which are
possibly conditional) to elements within a data structure. The path specified by
L.identity
is the empty path or []
. So, in the above traversal version, the
L.choose
expression either chooses to address the element at that point, by
returning L.identity
(it could also return []
), or it specifies a set of
paths to things in subelements
.
Here is a third approach using transform ops with a traversal (playground):
L.transform(['elements',
L.elems,
L.choose(elem => elem.time < 2
? L.removeOp
: ['subelements', L.elems, L.when(i => i < 3), L.removeOp])],
target)
Using transform ops might be a preferred approach when you don't need bidirectionality and need to perform different operations in different parts of a data structure.
You can also use transform ops in transforms that use L.seq
(playground):
L.transform(['elements',
L.elems,
L.seq([L.when(elem => elem.time < 2), L.removeOp],
['subelements', L.elems, L.when(i => i < 3), L.removeOp])],
target)
Note that the above use of L.seq
is arguably an improvement over the previous
use of L.seq
in the sense that the elements
array is only traversed once.