I would like to set an item at position x between two lists, as if they were the same list.
For example:
data Foo = Foo {
list1 :: [Char],
list2 :: [Char]}
foo = Foo ['a', 'b', 'c'] ['d', 'e', 'f']
setItem :: Int -> Char -> Foo -> Foo
setItem i c f = ???
For instance, setting element at position 5 would yield:
setItem 5 'X' foo
==> Foo ['a', 'b', 'c'] ['d', 'X', 'f']
I thought I could use optics/lens for this. Something like (using Optics and Labels):
setItem :: Int -> Char -> Foo -> Foo
setItem i c f = set ((#list1 <> #list2) % at i) c f
But this doesn't work:
No instance for (Semigroup (Optic k1 NoIx s0 t0 v0 v0))
I've looked into how to do this with optics, and you need to combine the two lenses using
adjoin, you can then get a traversal over the element with a particular index usingelementOf.These can be combined as follows:
I'm going to start by writing out the imports and data declarations in full:
Then the actual function looks like:
Two things to be aware of is that correct usage of
adjoinrelies on the targets of both lenses being disjoint; you shouldn't use it with the same lens (or lenses with overlapping targets) as different arguments.And
elementOftraverses the nth element of the traversal; it doesn't respect the indices of an IxTraversal, it takes any old Traversal and uses the position of each element within that traversal as the index.