Is there a shorthand for operations like `fromNewtype . f . toNewtype`?

  • A+
Category:Languages

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 and x' :: Sum Int. I would much rather type getPrime and getSum every time I use them, than coerce 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:

ala :: (Coercible a b, Coercible a' b')      => (a -> b)      -> ((a -> b) -> c -> b')        -> c      -> a'  

where

  • a is the unwrapped base type.
  • b is the newtype that wraps a.
  • a -> b is 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 foldMap.
  • a' the unwrapped final result. The unwrapping is handled by ala itself.

in your case, it would be something like:

ala Sum foldMap [1,2::Integer] 

"ala" functions can be implemented through means other than coerce, for example using generics to handle the unwrapping, or even lenses.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: