Practically speaking, such an optic is a slightly inconvenient Traversal
.
That's because, practically speaking, we use a Traversal
:
type Traversal s t a b = forall f. (Applicative f) => (a -> f b) -> (s -> f t)
for two things. Getting a list of a
s from an s
, which we can do with the Const
functor:
toListOf :: Traversal s t a b -> s -> [a]
toListOf t = getConst . t (Const . (:[]))
and replacing the a
s with b
s to turn the s
into a t
. One method is to use the State
functor, and ignoring issues with matching the counts of a
s and b
s, we have:
setListOf :: Traversal s t a b -> [b] -> s -> t
setListOf t bs s = evalState (t (\a -> state (\(b:bs) -> (b, bs))) s) bs
If we instead have an optic using a Monad
constraint:
type TraversalM s t a b = forall f. (Monad f) => (a -> f b) -> (s -> f t)
we can still perform these two operations. Since State
is a monad, the setListOf
operation can use the same implementation:
setListOfM :: Traversal s t a b -> [b] -> s -> t
setListOfM t bs s = evalState (t (\a -> state (\(b:bs) -> (b, bs))) s) bs
For toListOf
, there's no Monad
instance for Const [a]
, but we can use a Writer
monad to extract the a
values, as long as we have a dummy b
value to make the type checker happy:
toListOfM :: TraversalM s t a b -> b -> s -> [a]
toListOfM t dummy_b s = execWriter (t (\a -> tell [a] >> pure dummy_b) s)
or, since Haskell has bottom:
toListOfM' :: TraversalM s t a b -> s -> [a]
toListOfM' t s = execWriter (t (\a -> tell [a] >> pure undefined) s)
Self-contained code:
import Data.Functor.Const
import Control.Monad.State
import Control.Monad.Writer
type Traversal s t a b = forall f. (Applicative f) => (a -> f b) -> (s -> f t)
toListOf :: Traversal s t a b -> s -> [a]
toListOf t = getConst . t (Const . (:[]))
setListOf :: Traversal s t a b -> [b] -> s -> t
setListOf t bs s = evalState (t (\a -> state (\(b:bs) -> (b, bs))) s) bs
type TraversalM s t a b = forall f. (Monad f) => (a -> f b) -> (s -> f t)
toListOfM :: TraversalM s t a b -> b -> s -> [a]
toListOfM t dummy_b s = execWriter (t (\a -> tell [a] >> pure dummy_b) s)
toListOfM' :: TraversalM s t a b -> s -> [a]
toListOfM' t s = execWriter (t (\a -> tell [a] >> pure undefined) s)
setListOfM :: TraversalM s t a b -> [b] -> s -> t
setListOfM t bs s = evalState (t (\a -> state (\(b:bs) -> (b, bs))) s) bs
listItems :: Traversal [a] [b] a b
listItems = traverse
listItemsM :: TraversalM [a] [b] a b
listItemsM = mapM
main = do
-- as a getter
print $ toListOf listItems [1,2,3]
print $ toListOfM listItemsM 99 [1,2,3] -- dummy value
print $ toListOfM' listItemsM [1,2,3] -- use undefined
-- as a setter
print $ setListOf listItems [4,5,6] [1,2,3]
print $ setListOfM listItemsM [4,5,6] [1,2,3]