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
adjoin
relies 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
elementOf
traverses 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.