Recently migrated are the pages on [[worker wrapper]] and [[factory function]].

−

+

In general, the more information you place on the type level, the more

In general, the more information you place on the type level, the more

static checks you get -- and thus less chance for bugs.

static checks you get -- and thus less chance for bugs.

+

+

== Runtime Optimisation : smart constructors ==

+

+

Another use for smart constructors is to perform basic optimisations, often to obtain a normal form for constructed data. For example, consider a data structure representing addition and multiplication of variables.

+

+

<haskell>

+

data Expression = Variable String

+

| Add [Expression]

+

| Multiply [Expression]

+

</haskell>

+

+

In this data structure, it is possible to represent a value such as <tt>Add [Variable "a", Add [Variable "b", Variable "c"]]</tt> more compactly as <tt>Add [Variable "a", Variable "b", Variable "c"]</tt>.

+

+

This can be done automatically with smart constructors such as:

+

+

<haskell>

+

add :: [Expression] -> Expression

+

add xs = Add (concatMap fromAdd xs)

+

multiply :: [Expression] -> Expression

+

multiply xs = Multiply (concatMap fromMultiply xs)

+

+

fromAdd (Add xs) = xs

+

fromAdd x = [x]

+

fromMultiply (Multiply xs) = xs

+

fromMultiply x = [x]

+

</haskell>

[[Category:Idioms]]

[[Category:Idioms]]

+

[[Category:Glossary]]

Revision as of 18:33, 25 February 2012

Smart constructors

This is an introduction to a programming idiom for placing extra
constraints on the construction of values by using smart
constructors.

Sometimes you need guarantees about the values in your program beyond
what can be accomplished with the usual type system checks.
Smart constructors can be used for this purpose.

Consider the following problem: we want to be able to specify a data
type for electronic resistors. The resistors come in two forms, metal
and ceramic. Resistors are labelled with a number of bands, from 4 to 8.

One extra step has to be made though, to make the interface safe. When
exporting the type Resistor we need to hide the (unsafe)
constructors, and only export the smart constructors, otherwise a
reckless user could bypass the smart constructor:

And now obtain more detailed error messages, automatically generated for us:

> metalResistor 0
*** Exception: A.hs:4:18-23: Assertion failed

We at least now are given the line and column in which the error occured.

2 Compile-time checking : the type system

2.1 Enforcing the constraint statically

There are other ways to obtain numerical checks like this. The most
interesting are probably the static checks that can be done with Type arithmetic,
that enforce the number of bands at compile time, rather than runtime,
by lifting the band count into the type level.

In the following example, instead of checking the band count at runtime,
we instead lift the resistor band count into the type level, and have
the typecheck perform the check statically, using phantom types
and Peano numbers.

We thus remove the need for a runtime check, meaning faster code. A consequence
of this decision is that since the band count is now represented in the type,
it is no longer necessary to carry it around at runtime, meaning less data has
to be allocated.

Firstly, define some Peano numbers to represent the number of bands as types:

2.3 Summary

By using a standard encoding of numeric values on the type level we are able to
encode a bounds check in the type of a value, thus removing a runtime check,
and removing the need to store the numeric value at runtime. The code is safer,
as it is impossible to compile the program unless all resistors have the
correct number of bands.

An extension would be to use a decimal encoding for the integers (at the
expense of longer code).

2.4 Extensions

Further checks can be obtained by separating the metal and ceramic
values on the type level, so no function that takes a metal resistor can
be accidentally passed a ceramic one.

now, a function of resistors must have either a MetalResistor type, or a
CeramicResistor type:

foo :: MetalResistor ->Int
foo (MetalResistor n)= n

You can't write a function over both resistor types (other than a purely
polymorphic function).

2.5 Related work

These ideas are also discussed in Dimensionalized numbers
and on the old wiki
here (for
compile-time unit analysis error catching at the type level).
Recently migrated are the pages on worker wrapper and factory function.
In general, the more information you place on the type level, the more
static checks you get -- and thus less chance for bugs.

3 Runtime Optimisation : smart constructors

Another use for smart constructors is to perform basic optimisations, often to obtain a normal form for constructed data. For example, consider a data structure representing addition and multiplication of variables.