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 Int
andx' :: Sum Int
. I would much rather typegetPrime
andgetSum
every time I use them, thancoerce
everything and have one day made a catastrophic mistake.usefulness
coerce
does 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
ala
function available here, which has type:where
a
is the unwrapped base type.b
is the newtype that wrapsa
.a -> b
is 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 ofa
s) and return a wrapped resultb'
. In practice this function is almost alwaysfoldMap
.a'
the unwrapped final result. The unwrapping is handled byala
itself.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.