This repository was archived by the owner on Nov 6, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathch12
528 lines (338 loc) · 18.6 KB
/
ch12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
Introduction to Gofer 12. DIALOGUES: INPUT AND OUTPUT
12. DIALOGUES: INPUT AND OUTPUT
The Gofer system implements a subset of the facilities for programs
involving I/O described in the Haskell report [5]. In particular, this
makes it possible for Gofer programs to be run interactively, and to
make limited use of text files for both reading and writing. A
significant factor in the design of the Haskell I/O facilities is that
it allows the use of such programs without loss of referential
transparency.
12.1 Basic description
----------------------
Programs using the I/O facilities in Gofer are modelled by functions of
type Dialogue, defined by the type synonym:
type Dialogue = [Response] -> [Request]
In other words, a Gofer program produces a list of output values, each
of which may be thought of as a request for some particular input or
output action, and obtains the corresponding list of operating system
responses as its input. Note that the input list of responses will be
evaluated lazily; i.e. we can ensure that we do not attempt to obtain
the response to a given request until that request has been completed.
The current range of requests supported by Gofer is described by the
following datatype definition, taken from the standard prelude:
data Request = -- file system requests:
ReadFile String
| WriteFile String String
| AppendFile String String
-- channel system requests:
| ReadChan String
| AppendChan String String
-- environment requests:
| Echo Bool
Each response is an element of the type defined by the following
datatype definition, using an auxiliary datatype IOError to describe a
variety of error conditions that may occur:
data Response = Success
| Str String
| Failure IOError
data IOError = WriteError String
| ReadError String
| SearchError String
| FormatError String
| OtherError String
The following list describes the kind of I/O behaviour specified by
each form of Request and indicates the possible Response values that
may be obtained in each case:
o ReadFile string: Read contents of file named by "string".
49
Introduction to Gofer 12.1 Basic description
Possible responses to this request are:
o Str contents if the request is successful, where "contents"
is a string (evaluated lazily) containing the contents of the
file specified by the ReadFile request.
o Failure (SearchError name) occurs if file "name" cannot be
accessed.
o Failure (ReadError name) occurs if some other error occurs
whilst opening the file "name".
o WriteFile name string: Write the given "string" to the file
"name". If the file does not already exist, it is created before
attempting to write the value to file. If the file already exists
then it will be truncated to zero length before the write begins.
No response is obtained until the string argument has been fully
evaluated and its contents written to file. Possible responses
are:
o Success if the write to file was completed successfully.
o Failure (WriteError msg) if an error was detected whilst
trying to perform the output. If the problem occurred whilst
attempting to open the specified file, then "msg" contains
the filename, otherwise it contains a printable
representation of the evaluation error which occurred.
o AppendFile name string: Similar to the WriteFile request except
that the value of the given "string" is appended onto the file
"name" if that file already exists. The responses that may be
obtained from this request are the same as those for WriteFile.
o ReadChan name: Read from the input stream "name". Note that
it is an error to attempt to read from the same channel more than
once in the same program. Possible responses are:
o Str contents if the request is successful, where "contents"
is a string (evaluated lazily) containing the list of
characters entered on the input stream.
o Failure (SearchError name) if the named channel cannot be
found. The only input channel known to Gofer is the standard
input channel "stdin". For convenience, the standard prelude
defines the variable stdin bound to this string.
o Failure (ReadError name) if a ReadChan request for the named
channel has already been given by a previous request.
o AppendChan name string: Output "string" on channel "name". No
response is obtained until the string has been fully evaluated and
written to the named channel. Possible responses are:
o Success if the append to channel was completed successfully.
o Failure (SearchError name) if the named channel cannot be
50
Introduction to Gofer 12.1 Basic description
found. The only output channels known to Gofer are "stdout",
"stderr" and "stdecho" (which is actually just another name
for "stdout" in Gofer). For convenience, the standard
prelude defines variables stdout, stderr and stdecho bound to
the corresponding string values.
o Failure (WriteError msg) if an error is detected whilst
trying to perform the output. The string "msg" contains a
printable representation of the evaluation error which
occurred.
o Echo status: Set the echo status on the standard input channel
stdin to the given boolean value. If the echo status is True,
then user input will be echoed onto the screen as it is typed and
the usual line editing facilities (such a backspace or delete)
provided by the host system can be used to edit the input lines as
they are entered. If the echo status is False, then individual
characters may be read from the standard input channel without any
echo or line editing features.
Note that at most one Echo request can be used in a program, and
must precede any ReadChan request for stdin. If not set by an
explicit Echo request, the echo status defaults to True. Possible
responses are:
o Success if the request was completed successfully.
o Failure (OtherError msg) if the request could not be
completed either because a readChannel request for stdin has
already been processed, or because a previous Echo request
has already been given. The corresponding values of "msg"
are "stdin already in use" and "repeated Echo request"
respectively.
A simple example of a program using these facilities to output a short
message on the standard output stream is:
helloWorld :: Dialogue
helloWorld resps = [AppendChan stdout "hello, world"]
Any expression entered into Gofer of type "Dialogue" will be treated as
a Gofer program using I/O and will be executed accordingly:
? helloWorld
hello, world
(1 reduction, 28 cells)
?
Notice that without the explicit type declaration, the type that would
be inferred for helloWorld would be a -> [Request], and hence
helloWorld would not be executed as a Dialogue program. This point can
be illustrated using lambda expressions:
? \resps -> [AppendChan stdout "hello, world"]
v128
(1 reduction, 7 cells)
51
Introduction to Gofer 12.1 Basic description
? (\resps -> [AppendChan stdout "hello, world"]) :: Dialogue
hello, world
(1 reduction, 28 cells)
?
In many cases the structure of an expression is enough to fully
determine its type as Dialogue (or equivalently as [Response] ->
[Request]), in which case no explicit types are required to ensure that
the expression is treated as a Gofer program using I/O:
? \~[Success] -> [AppendChan stdout "hello, world"]
hello, world
(1 reduction, 29 cells)
?
Note the use of the irrefutable pattern ~[Success] for the lambda
expression in the last example; without this, the usual rules of
pattern matching as described in section 9 would force Gofer to try to
match the pattern [Success] against the list of responses, before the
corresponding request had been produced:
? \ [Success] -> [AppendChan stdout "hello, world"]
Aborting Dialogue:
{error "Attempt to read response before request complete"}
(50 reductions, 229 cells)
?
The next example takes a single string as a parameter and displays the
contents of the corresponding file:
showFile :: String -> Dialogue
showFile name ~(read:_) = [ReadFile name, AppendChan stdout result]
where result = case read of Str contents -> contents
Failure _ -> "Can't open " ++ name
With a few modifications, we can implement a similar program which
prompts for, and reads, a filename from the standard input and then
reads and displays the contents of that file as before. This program
is based on a similar example in the Haskell report [5]:
main ~(Success : ~(Str userInput : ~(r3 : _)))
= [ AppendChan stdout "Please type a filename: ",
ReadChan stdin,
ReadFile name,
AppendChan stdout (case r3 of Str contents -> contents
Failure _ -> "Can't open "
++ name)
] where (name : _) = lines userInput
52
Introduction to Gofer 12.2 Continuation style I/O
12.2 Continuation style I/O
---------------------------
As an alternative to the `stream-based' approach to programs using the
I/O facilities in Gofer, the standard prelude defines a family of
functions which enables such programs to be written in a `continuation'
style. The basic idea is to define a function corresponding to each
different kind of request, whose parameters include the values required
to make the request together with two continuations. The continuations
are functions describing "what to do next", one of which is used if the
request is successful, the other if the request fails.
As an example, the ReadFile request is represented by the function
"readFile" whose definition is equivalent to:
readFile name fail succ ~(r:rs) = ReadFile name : rest rs
where rest = case r of Str s -> succ s
Failure ioerror -> fail ioerror
The first thing to happen when a dialogue expression of the form
"readFile name fail succ" is evaluated is that the corresponding
request "ReadFile name" is added to the list of I/O requests. A new
dialogue value "rest" is chosen, depending on the response to the
ReadFile request, and the program continues by passing the remaining
part of the response list to "rest". The functions "succ" and "fail"
(called the success and failure continuations respectively) describe
the way in which the new dialogue "rest" is obtained.
The following example (edited a little to fit within the margins of this
document) shows how the readFile function described above can be used to
print the contents of a file called "test" on the display:
? readFile "test" (\ioerror resps -> [])
(\s resps->[AppendChan stdout s])
This is a test message
(4 reductions, 52 cells)
?
The success continuation "(\s resps->[AppendChan stdout s])" used here
receives the contents of the file "test" in the the parameter "s" and
uses an AppendChan request to output that string on the display. As
this example shows, the stream based approach of the previous section
can be combined with the continuation based style of I/O without any
difficulty. The failure continuation "(\ioerror resps -> [])" ignores
the error condition "ioerror" which caused the request to fail and
gives a dialogue which terminates immediately without any action. For
example, assuming that the file "Test" cannot be found:
? readFile "Test" (\ioerror resps -> [])
(\s resps->[AppendChan stdout s])
(4 reductions, 24 cells)
?
In practice, it is usually a good idea to produce some kind of
diagnostic message when an error occurs:
53
Introduction to Gofer 12.2 Continuation style I/O
? readFile "Test"
(\ioerror resps -> [AppendChan stdout (show' ioerror)])
(\s resps -> [AppendChan stdout s])
SearchError "Test"
(11 reductions, 59 cells)
?
In each of the examples above, the failure continuation has type
"FailCont" as defined by the following type synonym in the standard
prelude:
type FailCont = IOError -> Dialogue
Similarly, the success continuation, which takes a string representing
an input string and produces a new Dialogue has type "StrCont":
type StrCont = String -> Dialogue
A third kind of continuation is needed for those requests which return
a response of the form "Success" if successful (e.g. output
requests). In this case the continuation is simply another dialogue:
type SuccCont = Dialogue
The following list gives the type of each of the six functions
corresponding to the six different kinds of I/O request described in
the previous section. Full definitions for each of these functions are
given in appendix B:
readFile :: String -> FailCont -> StrCont -> Dialogue
writeFile :: String -> String -> FailCont -> SuccCont -> Dialogue
appendFile :: String -> String -> FailCont -> SuccCont -> Dialogue
readChan :: String -> FailCont -> StrCont -> Dialogue
appendChan :: String -> String -> FailCont -> SuccCont -> Dialogue
echo :: Bool -> FailCont -> SuccCont -> Dialogue
As an illustration of the use of these functions, we show how each of
the example programs from the previous section can be rewritten using
the continuation based style of I/O, starting with the program
"helloWorld":
helloWorld :: Dialogue
helloWorld = appendChan stdout "hello, world" abort done
In this case, the explicit type declaration is not actually required
since the type of the expression is completely determined by the type
of "appendChan". The failure continuation "abort" is equivalent to the
function "(\ioerror resps -> [])" described above and terminates the
program if an error occurs without any further action. In a similar
way, "done" is the trivial dialogue which terminates immediately
without any action. Both of these values are defined in the standard
prelude:
done :: Dialogue
done resps = []
54
Introduction to Gofer 12.2 Continuation style I/O
abort :: FailCont
abort ioerror = done
Using the same approach, the "showFile" and "main" programs from the
previous section are written as:
showFile :: String -> Dialogue
showFile name
= readFile name (\ioerror -> appendChan stdout
("Can't open " ++ name) abort done)
(\contents-> appendChan stdout contents abort done)
main :: Dialogue
main = appendChan stdout "Please type a filename: " abort
(readChan stdin abort
(\userInput -> let (name : _) = lines userInput in
readFile name
(\ioerror -> appendChan stdout ("Can't open " ++ name)
abort done)
(\contents -> appendChan stdout contents abort done)))
12.3 Interactive programs
-------------------------
One of the principal motivations for including facilities for I/O in
Gofer programs was to provide a way of using interactive programs as
described in [1]. An interactive program is represented by a function
of type String -> String mapping an input string of characters entered
at the keyboard into an output string to be displayed on the screen.
There are two functions defined in the standard prelude which can be
used to `execute' functions of this kind as interactive programs:
o "interact f" executes f::String->String as an interactive program
with echo on. This means that characters are read from the
keyboard a line at a time. The usual editing characters such as
backspace can be used to correct mistakes which are noticed before
the return key is pressed at the end of each line. The input
stream can be terminated by typing an end of file character at the
beginning of a line:
? interact (map toUpper)
This text was entered using the interact function
THIS TEXT WAS ENTERED USING THE INTERACT FUNCTION
^Z
(874 reductions, 1037 cells)
?
o "run f" behaves like "interact f" except that echo is turned off.
In this case, the only way of terminating the input stream without
reaching the end of the string produced by "f" is to use the
interrupt key:
? run (map toUpper)
ALTHOUGH THIS IS ENTERED IN LOWER CASE, IT STILL
APPEARS IN UPPER CASE !
55
Introduction to Gofer 12.3 Interactive programs
{Interrupted!}
(1227 reductions, 1463 cells)
?
[ASIDE: of these two functions, only "interact" is also included in the
standard prelude for Haskell, although "run" may also be added to a
Haskell system using the definition below.]
The definitions of "interact" and "run" provide further examples of
Gofer programs using simple I/O facilities:
interact :: (String -> String) -> Dialogue
interact f = readChan stdin abort
(\s -> appendChan stdout (f s) abort done)
run :: (String -> String) -> Dialogue
run f = echo False abort (interact f)
[EXERCISE for the interested reader: construct alternative definitions
for these functions using the stream based approach from section 12.1.]
56