6
votes

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?

1
You want a different classuser2407038
It does not make sense to do that: a functor is a higher order type.Willem Van Onsem
A third option is over from lens (or similar packages): given a lens for your field called unWidth, over unWidth does what you want (i.e. it lets you work with a monomorphic functor).duplode
The problem is not with newtype. Say data Width or newtype Box a and nothing changes (except laziness which is not important here).n. 1.8e9-where's-my-share m.
@n.m. Understood. I tried to title this question to match future searches. I don't anticipate any new Haskell users searching for "how to write a functor for a monomorphic container". Any suggestions on how to amend the title or content for both (1) accuracy, and (2) searchability?Bobby Eickhoff

1 Answers

10
votes

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