[Haskell-cafe] Are newtypes optimised and how much?

Christopher Done chrisdone at googlemail.com
Wed Oct 20 13:17:35 EDT 2010


On 20 October 2010 13:09, Simon Peyton-Jones <simonpj at microsoft.com> wrote:
> Yes, you can freely use Foo/unFoo.  There's no runtime penalty.  (In the jargon of GHC's intermediate language, Foo and unFoo translate to *type-safe casts*, which generate no executable code.
>
> That includes the 'newtype deriving' stuff too, and hence your uses of fromInteger etc.

Oh wow, excellent!

> No, this isn't optimised.  The trouble is that you write (map Foo xs), but GHC doesn't know about 'map'.  We could add a special case for map, but then you'd soon want (mapTree Foo my_tree).
>
> What you really want is to say is something like this.  Suppose my_tree :: Tree String.  Then you'd like to say
>        my_tree ::: Tree Foo
> meaning "please find a way to convert m_tree to type (Tree Foo), using newtype coercions.
>
> The exact syntax is a problem (as usual).  We have the technology now.  The question is how important it is.

I don't know whether it's so important for me at least. I was just
interested in how much optimisation it did.

On 20 October 2010 17:58, Gregory Crosswhite <gcross at phys.washington.edu> wrote:
> On 10/20/10 4:09 AM, Simon Peyton-Jones wrote:
>>
>> No, this isn't optimised.  The trouble is that you write (map Foo xs), but
>> GHC doesn't know about 'map'.  We could add a special case for map, but then
>> you'd soon want (mapTree Foo my_tree).
>
> How about a special case for fmap?  That seems like it should handle a lot
> of cases.

Personally I haven't had much use for mapping newtype
constructors/unconstructors.

My personal use case of newtypes unwrapping/wrapping is in passing
them to functions and in record fields, not so much unwarpping them
inside data types and trees across the board.

-- | Assign a review for a submission to a user.
assignReview :: TrackId -> UserId -> SubmissionId -> Model ()
assignReview tid uid sid = do
  insert T.review $
     F.uid        <<- unUserId uid
   # F.submission <<- unSubmissionId sid
   # F.trackId    <<- unTrackId tid

This is the simplest function I could find. However, consider if in my
busy hacking I accidentally get the argument order incorrect in the
definition, or the call, with newtypes I can't mismatch them. This has
stopped me doing bad things a few times in my haste.

Another nice thing I've found is combining them with view patterns:

-- | Submit a review.
submitReview :: UserId -> SubmissionId -> [ReviewField] -> Model ()
submitReview (unUserId -> uid) (unSubmissionId -> sid) rs = do
  forM_ rs $ \ReviewField{..} ->
     ...

The next ten lines or so use uid and sid. This makes the submitReview
function kind of "guarded" from being given the wrong values. Inside
it can do what it wants. You might point out that now that I've
unwrapped them I can pass them to some other function willy nilly, but
of course if a function needs a submission id, then it needs a
SubmissionId. So I find this really nice. And of course I use the
deriving extensively:

newtype SubmissionId = SubmissionId { unSubmissionId :: Int }
  deriving (Show,Num,Eq,Ord,Enum,Integral,Real,ShowConstant,JSON)

I have a types file with 46 newtypes, zero type aliases, and ah, 47
data types. GHCi gets kind of slow when dealing with it. My solution
is to have an Emacs shortcut to build the project with cabal every so
often so that GHCi can load the .o files in an instant.


More information about the Haskell-Cafe mailing list