Input/output operations are essential in image processing, but judging from the above example, it has to be admitted that Miranda would not particularly good at expressing I/O . Two issues may be pointed o u t
In order to communicate with outside world, encoding/decoding of foreign data needs to be carried out, such as converting a four-byte integer to a number, or a one-byte character to a number. This problem would not arise as long as a file is written and read by one language
in a persistent way. For example, Miranda provides the r e a d v a ls function which can read a
file directly if the file has been written by Miranda. Also in Haskell, if a file has been written
by a W r it e B in F ile request, the file can be read in by a R e a d B in F ile request. But this
1. It would be straight forward to avoid this cost by storing the image in memory in raster order. However, for ease of explanation we have not done this. See Section 3.2.
facility is implementation dependent and data persistency is not maintained even between different Haskell compilers ITrinder92a]. Although this kind of inconvenience would be inevitable in I/O in any case, it may not be wise to get such low level operations done by a high level language such as Miranda because of its execution speed. One possible solution to this data persistency problem would be for a language to incorporate code to import foreign data written in another language. For example, the new Glasgow Haskell compiler has a facility which allows arbitrary C functions to be called from the functional program without losing referential transparency [Hall92aJ.
The other issue of note is related to polynwrphic typing. The above functions,
rea d lm a g e and w r i t e Image, handle images of numbers only. In order to handle other kinds
of images such as boolean or character images, a separate function with a unique name for each type of images will be necessary. A polymorphic function accepts any type as long as the function behaves exactly in the same way for any type, but in I/O this is not the case, i.e. there is no way to convert polymorphic types to a list of characters. This means that, although in the heart of functional programming polymorphic typing works quite nicely (Section 3.6.4), once I/O is involved, it becomes necessary for programmers to be aware that which concrete types are being processed.
The problem of polymorphic typing and communication with the outside world can be
found in Miranda's special function show [Research Software Limited89a]. The function has
been defined to cope with the need to convert an arbitrary value to its printable representation without requiring an infinite number of functions. However, the show function is rather difficult to use because it seems like a polymorphic function but actually it is not. The type of
show must be determined monomorphically. For example, if the following function is defined
without its type signature:
myShow x = "hello"++show x++"world\n"
the compiler will infer the type of myShow to be *-> [ c h a r ] and the type of x to be *. Since
this usage of show is polymorphic the compiler reports an error. Hence, a type signature is
essential in such a case.
The above discussion would be a typical example of why overloading is desirable. Haskell's facility of function overloading combined with polymorphic typing [Wadler89a] may become a solution to the problem, since it allows switching operations depending on the
- 124-
type of data to be processed. Once separate I/O functions under the same name have been defined for separate instances in a type class, application programmers do not have to pay attention to which concrete types are being processed.
7.3 The Abstract Data Type: function images
An abstract data type called/wncfion image or f im age implements a non-pixel image. It is
basically a function from a coordinate to a pixel. A coordinate is identical to the abstract data
type x>ector described in Section 6.5.1, which is essentially a pair of x and y. Since a function
image is the function itself, various processes can be written in functional languages using function compositions. In this section, geometric transformations of a function image are implemented as example.
7.3.1 ly p e signatures
The principal reason for using abstract data types would be to encapsulate data and operations together and hide detailed implementation from application programmers. For them it is sufficient to understand the type signatures for the abstract data type and its operations.
The following is the definition of the abstract data type for a function imageor fim age:
filename « [char] abstype fimage * with makeFImage::(coord->*)->fImage * writeFImage::coord->vector->fImage num->filename->[sysmessage] displayFImage::coord->vector->fImage *->[[*]] lookUpFImage::coord->fImage *->* translateFImage:;vector->fImage *->fImage * scaleFImage:;vector->fimage *->fimage * rotateFImage;:num->fImage *->fimage *
As in the image data structures introduced so far, the type of a pixel is parameterised as the type variable *. makeFImage converts a function from a coordinate to a pixel into a function image.
Here we address again the problem with the polymorphic data types and 1/O discussed in Section 7.2.2, Although a function image can be an image of any type, when an image is written to a file it is necessary to know exactly what the type of its pixel is. In the current
implementation, only an image of numbers can be handled (see the type signature of w rite F lm a g e ). If the other types of images are to be dealt w ith, a separate function for each type must be defined under a unique name.