A pattern that presents itself the more often the more type safety is being introduced via newtype is to project a value (or several values) to a newtype wrapper, do some operations, and then retract the projection. An ubiquitous example is that of Sum and Product monoids:
λ x + y = getSum $ Sum x `mappend` Sum y
λ 1 + 2
3
I imagine a collection of functions like withSum, withSum2, and so on, may be automagically rolled out for each newtype. Or maybe a parametrized Identity may be created, for use with ApplicativeDo. Or maybe there are some other approaches that I could not think of.
I wonder if there is some prior art or theory around this.
P.S. I am unhappy with coerce, for two reasons:
safety I thought it is not very safe. After being pointed that it is actually safe, I tried a few things and I could not do anything harmful, because it requires a type annotation when there is a possibility of ambiguity. For example:
λ newtype F = F Int deriving Show λ newtype G = G Int deriving Show λ coerce . (mappend (1 :: Sum Int)) . coerce $ F 1 :: G G 2 λ coerce . (mappend (1 :: Product Int)) . coerce $ F 1 :: G G 1 λ coerce . (mappend 1) . coerce $ F 1 :: G ... • Couldn't match representation of type ‘a0’ with that of ‘Int’ arising from a use of ‘coerce’ ...But I would still not welcome
coerce, because it is far too easy to strip a safety label and shoot someone, once the reaching for it becomes habitual. Imagine that, in a cryptographic application, there are two values:x :: Prime Intandx' :: Sum Int. I would much rather typegetPrimeandgetSumevery time I use them, thancoerceeverything and have one day made a catastrophic mistake.usefulness
coercedoes not bring much to the table regarding a shorthand for certain operations. The leading example of my post, that I repeat here:λ getSum $ Sum 1 `mappend` Sum 2 3— Turns into something along the lines of this spiked monster:
λ coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer 3— Which is hardly of any benfit.
Your "spiked monster" example is better handled by putting the summands into a list and using the
alafunction available here, which has type:where
ais the unwrapped base type.bis the newtype that wrapsa.a -> bis the newtype constructor.((a -> b) -> c -> b')is a function that, knowing how to wrap values of the base typea, knows how to process a value of typec(almost always a container ofas) and return a wrapped resultb'. In practice this function is almost alwaysfoldMap.a'the unwrapped final result. The unwrapping is handled byalaitself.in your case, it would be something like:
"ala" functions can be implemented through means other than
coerce, for example using generics to handle the unwrapping, or even lenses.