Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: Functor instance for newtype

Tags:

haskell

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?

like image 971
Bobby Eickhoff Avatar asked Mar 01 '18 13:03

Bobby Eickhoff


1 Answers

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
like image 86
leftaroundabout Avatar answered Nov 17 '22 04:11

leftaroundabout