Haskell DISCUSSION

Haskell data vs newtype vs type: which should I use to wrap a Double as Volts or Meters?

Started by karim Haskell newtypedata declarationtype synonymtype safetyzero-cost wrapper
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

Haskell data vs newtype vs type: which should I use to wrap a Double as Volts or Meters?

karim Haskell Forum
#1

My signal-processing code passes many Double values around: volts, millivolts, seconds, hertz. I want the compiler to stop me from passing a frequency where a time is expected. I can write type Volts = Double, newtype Volts = Volts Double or data Volts = Volts Double, and all three compile.

What are the real differences between them, in type safety and at run time, and which one is appropriate for this?

Community replies 5

Re: Haskell data vs newtype vs type: which should I use to wrap a Double as Volts or Meters?

#2

type only creates a synonym. type Volts = Double and type Hertz = Double are both just Double to the compiler, so a function expecting Volts happily accepts a Hertz. Synonyms are for readability, for example type Sample = (Double, Double), and give no protection at all.

newtype and data both create a genuinely new type that must be wrapped and unwrapped explicitly with its constructor, so mixing up Volts 3.3 and Hertz 50 becomes a compile-time type error. For your purpose the choice is between those two.

Re: Haskell data vs newtype vs type: which should I use to wrap a Double as Volts or Meters?

#3

newtype is restricted to exactly one constructor with exactly one field, and in exchange it has no run-time cost. The wrapper exists only for the type checker; in the compiled program a Volts is represented exactly like the Double inside it, and wrapping or unwrapping is free.

data Volts = Volts Double creates a real heap object containing a pointer to the number, so each value has an extra level of indirection and an extra allocation. For a single-field wrapper, newtype is the standard choice. data is what you need as soon as there are several fields or several constructors, such as data Reading = Reading Volts Seconds.

Re: Haskell data vs newtype vs type: which should I use to wrap a Double as Volts or Meters?

#4

There is also a semantic difference involving laziness. With data, the constructor is a separate layer that can itself be unevaluated, so undefined and Volts undefined are different values. With newtype they are the same, because there is no layer. You can observe it: case undefined of Volts _ -> 1 returns 1 if Volts is a newtype, since there is nothing to evaluate, but crashes if it is a data type, since matching must evaluate to the constructor.

This rarely matters in application code, but it explains why a data type with a strict field, data Volts = Volts !Double, is close to a newtype and still not identical.

Re: Haskell data vs newtype vs type: which should I use to wrap a Double as Volts or Meters?

#5

The cost of a newtype is boilerplate: Volts 1.5 + Volts 2.0 does not compile until Volts has a Num instance. You can derive the common classes directly, deriving (Show, Eq, Ord), and with the GeneralizedNewtypeDeriving extension also Num, which reuses the instance of the wrapped type. Think about whether you want that, though: a derived Num lets you multiply volts by volts and still call the result volts.

Often a few named functions are better, such as addV (Volts a) (Volts b) = Volts (a + b) and scaleV k (Volts a) = Volts (k * a). Conversions between whole containers are free with coerce from Data.Coerce, which turns a [Volts] into a [Double] without traversing the list.

Re: Haskell data vs newtype vs type: which should I use to wrap a Double as Volts or Meters?

#6

Newtypes have a second job besides units: selecting a different class instance for the same underlying type. A type can have only one Monoid instance, but numbers can be combined by addition or by multiplication, so the base library wraps them: getSum (foldMap Sum [1, 2, 3, 4]) is 10 and getProduct (foldMap Product [1, 2, 3, 4]) is 24.

Combined with a module that exports the type but not its constructor, a newtype also lets you enforce invariants: callers can only obtain a value through a checking function such as mkPercent :: Double -> Maybe Percent.

TEP COMMUNITY