To level set, here's a simple functor:
data Box a = Box a deriving (Show)
instance Functor Box where
fmap f (Box x) = Box (f x)
This allows us to operate "inside the box":
> fmap succ (Box 1)
Box 2
How do I achieve this same syntactic convenience with a newtype? Let's say I have the following:
newtype Width = Width { unWidth :: Int } deriving (Show)
newtype Height = Height { unHeight :: Int } deriving (Show)
This is a bit clunky:
> Width $ succ $ unWidth (Width 100)
Width {unWidth = 101}
This would be nice:
> fmap succ (Width 100) -- impossible?
Width {unWidth = 101}
Of course, I can't make Width or Height an instance of a Functor since neither has kind * -> *
. Although, syntactically they feel no different than Box, and so it seems like it should be possible to operate on the underlying value without all of the manual wrapping and unwrapping.
Also, it isn't satisfying to create n functions like this because of the repetition with every new newtype:
fmapWidth :: (Int -> Int) -> Width -> Width
fmapHeight :: (Int -> Int) -> Height -> Height
How do I lift a function on Ints to be a function on Widths?
First note that newtype
is no hurdle here – you can parameterise these just as well as data
, and then you have an ordinary functor. Like
{-# LANGUAGE DeriveFunctor #-}
newtype WidthF a = Width { unWidth :: a } deriving (Show, Functor)
type Width = WidthF Int
I wouldn't consider that a good idea, though. Width
shouldn't be a functor; it doesn't make sense to store non-number types in it.
One option as user2407038 suggests is to make it a “monomorphic functor”
import Data.MonoTraversable (MonoFunctor(..))
newtype Width = Width { unWidth :: Int } deriving (Show)
instance MonoFunctor Width where
omap f (Width w) = Width $ f w
That too doesn't seem sensible to me – if you map number operations thus in one generic way, then you might just as well give Width
instances for Num
etc. and use these directly. But then you hardly have better type-system guarantees than a simple
type Width = Int
which can easily be modified without any help, the flip side being that it can easily be mishandled without any type system safeguards.
Instead, I think what you want is probably this:
import Control.Lens
data Box = Box {
width, height :: Int }
widthInPx, heightInPx :: Lens' Box Int
widthInPx f (Box w h) = (`Box`h) <$> f w
heightInPx f (Box w h) = (Box w) <$> f h
Then you can do
> Box 3 4 & widthInPx %~ (*2)
Box 6 4
> Box 4 2 & heightInPx %~ succ
Box 4 3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With