6

Real world haskell says:

we will hide the details of our parser type using a newtype declaration

I don't get how we can hide anything using the newtype. Can anyone elaborate? What are we trying to hide and how do we do it.

data ParseState = ParseState {
  string :: L.ByteString
, offset :: Int64           -- imported from Data.Int
} deriving (Show)


newtype Parse a = Parse {
    runParse :: ParseState -> Either String (a, ParseState)
}
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159

2 Answers2

12

The idea is to combine modules + newtypes to keep people from seeing the internals of how we implement things.

-- module A
module A (A, toA) where -- Notice we limit our exports
newtype A = A {unA :: Int}

toA :: Int -> A
toA = -- Do clever validation

-- module B
import A
foo :: A
foo = toA 1 -- Must use toA and can't see internals of A

This prevents from pattern matching and arbitrarily constructing A. This let's our A module make certain assumptions about A and also change the internals of A with impunity!

This is particularly nice because at runtime the newtypes are erased so there's almost no overhead from doing something like this

daniel gratzer
  • 52,833
  • 11
  • 94
  • 134
  • 2
    I would add that this also allows a library developer to _change_ the internals of a type without affecting users of it. The point of hiding your type isn't just to keep users from performing illegal operations, it also keeps the developer from breaking backwards compatibility in order to add new features. – bheklilr Nov 09 '13 at 04:40
  • Can you do the same trick with `data`? Can you give an example of A with exposed internals. – Trident D'Gao Nov 09 '13 at 04:45
  • Can I expose only certain data constructors or fields of a type? – Trident D'Gao Nov 09 '13 at 04:49
  • @AlekseyBykov `data` also works. To only export certain data constructors or field names, list them in the export list in parentheses after the data/newtype name, e.g. `module A(A(A), toA)` – Ørjan Johansen Nov 09 '13 at 05:14
  • 1
    Why is newtype so special then? – Trident D'Gao Nov 09 '13 at 05:18
  • 1
    @AlekseyBykov, `newtype` is just a wrapper which is meaningful only to the type system. It is not present at runtime, and it does not impose any runtime overhead, contrary to `data`. – Vladimir Matveev Nov 09 '13 at 08:19
  • There is also a subtler distinction between them which is that if you open up the constructor of a `data` type then there is a chance you might get bottom (such as an infinite loop), but when you open up a `newtype` type then you are guaranteed to never get bottom. – Gregory Crosswhite Nov 09 '13 at 12:01
  • @bheklilr That's already in there, see second to last sentence – daniel gratzer Nov 09 '13 at 12:16
  • @GregoryCrosswhite Can you explain? `newtype Foo = Foo Int; a = Foo undefined` – daniel gratzer Nov 09 '13 at 12:25
  • @jozefg If we let `a = Foo undefined`, then `case a of Foo _ -> ()` will return `()` if `Foo` was defined using `newtype` and bottom if it was defined using `data`. – Gregory Crosswhite Nov 09 '13 at 13:05
  • @GregoryCrosswhite Um actually, it doesn't. At least not for me. Data types aren't necessarily strict in their fields – daniel gratzer Nov 09 '13 at 16:04
  • 3
    @jozefg GregoryCrosswhite had the right idea, but slightly wrong execution. Compare `case undefined of Foo _ -> ()` when `Foo` is the constructor of a `data` type and of a `newtype` type. – Daniel Wagner Nov 09 '13 at 18:18
  • @DanielWagner Ah I see – daniel gratzer Nov 09 '13 at 19:43
6

You hide details by not exporting stuff. So there's two comparisons to make. One is exported vs. not exported:

-- hidden: nothing you can do with "Parse a" values -- though
-- you can name their type
module Foo (Parse) where
newtype Parse a = Parse { superSecret :: a }

-- not hidden: outsiders can observe that a "Parse a" contains
-- exactly an "a", so they can do anything with a "Parse a" that
-- they can do with an "a"
module Foo (Parse(..)) where
newtype Parse a = Parse { superSecret :: a }

The other is more subtle, and is the one RWH is probably trying to highlight, and that is type vs. newtype:

-- hidden, as before
module Foo (Parse) where
newtype Parse a = Parse { superSecret :: a }

-- not hidden: it is readily observable that "Parse a" is identical
-- to "a", and moreover it can't be fixed because there's nothing
-- to hide
module Foo (Parse) where
type Parse a = a
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380