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
λ x + y = getSum $ Sum x `mappend` Sum y λ 1 + 2 3
I imagine a collection of functions like
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 Intand
x' :: Sum Int. I would much rather type
getSumevery time I use them, than
coerceeverything and have one day made a catastrophic mistake.
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
ala function available here, which has type:
ala :: (Coercible a b, Coercible a' b') => (a -> b) -> ((a -> b) -> c -> b') -> c -> a'
ais the unwrapped base type.
bis the newtype that wraps
a -> bis the newtype constructor.
((a -> b) -> c -> b')is a function that, knowing how to wrap values of the base type
a, knows how to process a value of type
c(almost always a container of
as) and return a wrapped result
b'. In practice this function is almost always
a'the unwrapped final result. The unwrapping is handled by
in your case, it would be something like:
ala Sum foldMap [1,2::Integer]