Mapping the value inside a newtype

Viewed 796

Assume the following newtype is defined:

newtype A a = A a

And there is a function:

f :: A a -> A a

Now suppose I define another newtype, which contains A a:

newtype B a = B (A a)

And then I'd like to define a function fb which operates on B a but just uses f:

fb :: B a -> B a
fb (B x) = B (f x)

Now this is quite inconvenient since I have to unwrap and wrap the value inside an element of type B a. This won't be that bad if I only had to define one such a fb but if there are a lot of them this becomes quite tedious.

I'd be nice if there would be a type-class with a function:

(<$$>) :: k a -> k b -> h (k a) -> h (k b)

So that fb can be rewritten as:

fb = (f <$$>)

Maybe such an abstraction already exists, but I cannot find it.

3 Answers

One solution is to use the newtype-generics package, in particular the over function:

{-# LANGUAGE DeriveGeneric #-}
import Control.Newtype (Newtype, over)
import GHC.Generics

newtype A a = A a

newtype B a = B (A a) deriving (Generic)

instance Newtype (B a)

f :: A a -> A a
f = undefined

fb :: B a -> B a
fb = over B f

Notice that over requires the outer B constructor as a parameter, not only the function f.

If you like danidiaz's answer, you may like this somewhat more "modern" version of over:

mover :: (Coercible o n, Coercible o' n')
      => (o -> n)
      -> (o' -> n')
      -> (o -> o') -> n -> n'
mover _pack _pack' = coerce

This skips all the instances in favor of a bit more explicit passing.

Actually one of the main reasons to use newtype is to derive new Type Class instances for a new type which is wrapped over an existing one.

So its quite simple. You are just expected to write an instance for Functor class and that's it.

newtype Team a = Team a deriving (Show , Eq)

instance Functor Team where
  fmap f (Team x) = Team (f x)

newtype League a = League (Team a) deriving (Show , Eq)

instance Functor League where
  fmap f (League x) = League (fmap f x)


upgradeTeam :: (Int -> Int) -> Team [Int] -> Team [Int]
upgradeTeam f = fmap (map f)

upgradeLeague :: (Int -> Int) -> League [Int] -> League [Int]
upgradeLeague f = fmap (map f) 

prependToLeague :: League [Int] -> Int -> League [Int]
prependToLeague x n = fmap (n:) x

*Main> upgradeTeam (+1) (Team [0,1,2,3])
Team [1,2,3,4]

*Main> upgradeLeague (*2) (League (Team [1,2,3,4]))
League (Team [2,4,6,8])

*Main> prependToLeague (League (Team [2,4,6,8])) 42
League (Team [42,2,4,6,8])
Related