
\section{\label{discussion}Discussion}

     The algorithms described involve state transition in an essential
way, but it is doubtful whether their description would have been any
clearer in terms of variables and assignments.  The explicit
representation of sequences as infinite lists seems just as natural.
In some ways the list representation is closer to traditional
mathematical notation, where one typically represents state
transitions by introducing a subscripted sequence of states.

     The implementations in section \ref{implementations} create an
``array of streams''.  An alternative approach is to create a ``stream
of arrays'', in which each update is performed on the whole lattice to
create a new global state.  It only involves minor changes to the
implementation to create such a stream of arrays
directly.  Alternatively one can feed the results of the given
implementation to a pipeline of \verb$felems$, \verb$transpose$ and
\verb$map (listForm l r)$ to produce a list of arrays.  There are some
advantages to the ``array of streams'' approach (apart from the fact
that it models process-parallelism).  Some existing Haskell
implementations do not handle array accesses very efficiently, and in this
approach the array operations are confined to an initial stage where
the streams are ``tied together''.  In the ``stream of arrays''
approach array operations are needed in all updates.  On the other hand
it does seem more natural to have a list of arrays when it comes to
processing the data (the set of configurations) generated by a
simulation.  Appendix \ref{running} ends with a simple example
of this kind of data processing.

     In any case, once the ideas about lattices and arrays in section
\ref{lattices} are absorbed, implementation of the three
algorithms is very straightforward.  It was worth the effort of doing
the geometry carefully, for the level of generality it gave in
the implementations.  For example, apart from the definition of
\verb$colour$, the modifications made to the XY model simulation in
section \ref{lgtImplementation} to encompass lattice gauge theory
did not actually invalidate the code as an implementation of the XY
simulation---the final version works for systems with variables defined
on arbitrary dimension cells of an arbitrary dimension lattice (given
an appropriate instance of the \verb$Cell$ class).
Achieving this level of abstraction depended on
both ordinary (parametric) polymorphism and overloading,
together with use of higher order functions.

     The price one pays for this generality is that while all the
programs given in this article are executable (and have been tested),
they run too slowly to be regarded as production simulation programs.
For example, the Poisson solver was benchmarked on some small test
cases, and compared to a straightforwardly coded Fortran equivalent.
The Haskell program ran several ($\geq 4$) orders of magnitude more slowly
than the Fortran.  This does not come as a surprise.
Similar relative performances are reported in \cite{scientific4}.  Most of the
performance degradation can be attributed to the deliberate adoption of
a very high level of abstraction in the Haskell programs, with
little or no concern for efficient execution.  (In our experience
Haskell code written in Fortran-like {\em style} is less extremely slow.
But adopting a Fortran style of programming somewhat defeats
the object of functional programming.)

     Unless compilers for functional languages make very rapid
progress, it is difficult to imagine that the kind of programs
given in this article could be competitive in the r\^{o}le of
number-crunching production codes.  The programs themselves could
be optimised to gain significant factors in speed (and efficiency of
memory utilisation).  Higher order functions could be used more
sparingly, and probably ``granularity'' could be increased in various
ways.  A few efforts were made in this direction, but these were
hindered by difficulty in understanding the behaviour (in terms of
execution speed and memory utilisation) of the lazy programs.  Programmers
who need speed are usually happy to invest some effort in optimising
their own codes, which they often do better than compilers, provided
they have a reasonably accurate idea of what goes on during execution.
It is relatively difficult to form such a mental model for a lazy
functional language; good profiling tools may make the job
easier \cite{profiling}.

     As they stand, though, the programs capture the
essence of the algorithms in a way that a production Fortran
code probably never could.  At the very least, programs like
this must have a r\^{o}le in specification or prototyping.

