tl;dr: is it possible to use any of the lens
family of abstractions to wrap/unwrap any arbitrary newtype
(that provides an instance for such abstractions)?
I'll motivate my question by a simple example, based on a true story. Suppose I define the following newtype
:
newtype FreeMonoid a = FreeMonoid { asMap :: Map a Int }
which is used to represent terms of the form:
a0 <> a1 <> ... <> an-1
We can represent free-monoids as lists:
instance Ord a => IsList (FreeMonoid a) where
type Item (FreeMonoid a) = a
fromList xs = FreeMonoid $ Map.fromListWith (+) $ zip xs (repeat 1)
toList (FreeMonoid p) = do
(x, n) <- Map.toList p
genericReplicate n x
Two examples of free-monoids are sequences of sum and sequences of products:
type FreeSum a = FreeMonoid (Sum a)
type FreeProduct a = FreeMonoid (Product a)
Where Sum
and Product
are defined in Data.Monoid
. Now we could define fromList
and toList
operations for FreeSum
and
FreeProduct
as follows:
fromListSum :: Ord a => [a] -> FreeSum a
fromListSum = fromList . (Sum <$>)
fromListProduct :: Ord a => [a] -> FreeProduct a
fromListProduct = fromList . (Product <$>)
But this has quite a lot of boilerplate. It'd be nicer if we could simply say:
fromListW :: (Ord a, Wrapper f) => [a] -> FreeMonoid (f a)
fromListW = fromList . (wrap <$>)
where wrap
is some operation of the (hypotetical) Wrapper
class were:
wrap :: a -> f a
unwrap :: f a -> a
Similarly, I'd like to be able to write a function:
toListW :: (Ord a, Wrapper f) => FreeMonoid (f a) -> [a]
toListW = (unwrap <$>) . toList
Lenses seem to provide such an abstraction in Control.Lens.Wrapped
(for which Sum
and Product
in this example are instances of the typeclasses there!). However my attempts to understand and use the abstractions in this module have failed. For instance:
fromListW :: (Ord a, Wrapped (f a)) => [a] -> FreeMonoid (f a)
fromListW = fromList . (Wrapped <$>)
won't work since the argument is not a list of Unwrapped (f a)
.
So my question is:
Wrapper
class?The "problem" is that you're using Wrapped
, which is really meant to be a convenience pattern synonym and not a wrapping "constructor". Because it's designed to support polymorphic wrapping, you need to assert that your type can be rewrapped:
fromListW :: (Rewrapped a a, Ord a) => [Unwrapped a] -> FreeMonoid a
fromListW = fromList . (Wrapped <$>)
This then works as expected:
> let x = [1,2,3]
> fromListW x :: FreeMonoid (Sum Int)
FreeMonoid {asMap = fromList [(Sum {getSum = 1},...
> fromListW x :: FreeMonoid (Product Int)
FreeMonoid {asMap = fromList [(Product {getProduct = 1},...
>
I think a more idiomatic lens implementation would be:
fromListW :: (Rewrapped a a, Ord a) => [Unwrapped a] -> FreeMonoid a
fromListW = fromList . view (mapping _Unwrapped)
This still requires the Rewrapped a a
constraint, but you can use the non-polymorphic _Unwrapped'
instead:
fromListW :: (Wrapped a, Ord a) => [Unwrapped a] -> FreeMonoid a
fromListW = fromList . view (mapping _Unwrapped')
which looks a little more natural.
The toListW
implementation would have similar structure:
toListW :: (Wrapped a, Ord a) => FreeMonoid a -> [Unwrapped a]
toListW = view (mapping _Wrapped') . toList