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
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
|
% ------------------------------------------------------------------------------
% $Id: PrelIO.lhs,v 1.18 2001/01/11 17:25:57 simonmar Exp $
%
% (c) The University of Glasgow, 1992-2000
%
\section[PrelIO]{Module @PrelIO@}
This module defines all basic IO operations.
These are needed for the IO operations exported by Prelude,
but as it happens they also do everything required by library
module IO.
\begin{code}
{-# OPTIONS -fno-implicit-prelude -#include "cbits/stgio.h" #-}
module PrelIO where
import PrelBase
import PrelIOBase
import PrelHandle -- much of the real stuff is in here
import PrelNum
import PrelRead ( Read(..), readIO )
import PrelShow
import PrelMaybe ( Maybe(..) )
import PrelPtr
import PrelList ( concat, reverse, null )
import PrelPack ( unpackNBytesST, unpackNBytesAccST )
import PrelException ( ioError, catch, catchException, throw )
import PrelConc
#ifndef __PARALLEL_HASKELL__
#define FILE_OBJECT (ForeignPtr ())
#else
#define FILE_OBJECT (Ptr ())
#endif
\end{code}
%*********************************************************
%* *
\subsection{Standard IO}
%* *
%*********************************************************
The Prelude has from Day 1 provided a collection of common
IO functions. We define these here, but let the Prelude
export them.
\begin{code}
putChar :: Char -> IO ()
putChar c = hPutChar stdout c
putStr :: String -> IO ()
putStr s = hPutStr stdout s
putStrLn :: String -> IO ()
putStrLn s = do putStr s
putChar '\n'
print :: Show a => a -> IO ()
print x = putStrLn (show x)
getChar :: IO Char
getChar = hGetChar stdin
getLine :: IO String
getLine = hGetLine stdin
getContents :: IO String
getContents = hGetContents stdin
interact :: (String -> String) -> IO ()
interact f = do s <- getContents
putStr (f s)
readFile :: FilePath -> IO String
readFile name = openFile name ReadMode >>= hGetContents
writeFile :: FilePath -> String -> IO ()
writeFile name str = do
hdl <- openFile name WriteMode
hPutStr hdl str
hClose hdl
appendFile :: FilePath -> String -> IO ()
appendFile name str = do
hdl <- openFile name AppendMode
hPutStr hdl str
hClose hdl
readLn :: Read a => IO a
readLn = do l <- getLine
r <- readIO l
return r
\end{code}
%*********************************************************
%* *
\subsection{Simple input operations}
%* *
%*********************************************************
Computation @hReady hdl@ indicates whether at least
one item is available for input from handle {\em hdl}.
@hWaitForInput@ is the generalisation, wait for \tr{n} milliseconds
before deciding whether the Handle has run dry or not.
If @hWaitForInput@ finds anything in the Handle's buffer, it immediately returns.
If not, it tries to read from the underlying OS handle. Notice that
for buffered Handles connected to terminals this means waiting until a complete
line is available.
\begin{code}
hReady :: Handle -> IO Bool
hReady h = hWaitForInput h 0
hWaitForInput :: Handle -> Int -> IO Bool
hWaitForInput handle msecs =
wantReadableHandle "hWaitForInput" handle $ \ handle_ -> do
rc <- inputReady (haFO__ handle_) (msecs::Int) -- ConcHask: SAFE, won't block
case (rc::Int) of
0 -> return False
1 -> return True
_ -> constructErrorAndFail "hWaitForInput"
\end{code}
@hGetChar hdl@ reads the next character from handle @hdl@,
blocking until a character is available.
\begin{code}
hGetChar :: Handle -> IO Char
hGetChar handle = do
c <- mayBlockRead "hGetChar" handle fileGetc
return (chr c)
{-
If EOF is reached before EOL is encountered, ignore the
EOF and return the partial line. Next attempt at calling
hGetLine on the handle will yield an EOF IO exception though.
-}
hGetLine :: Handle -> IO String
hGetLine h = do
buffer_mode <- wantReadableHandle "hGetLine" h
(\ handle_ -> do return (haBufferMode__ handle_))
case buffer_mode of
NoBuffering -> hGetLineUnBuffered h
LineBuffering -> hGetLineBuf' []
BlockBuffering _ -> hGetLineBuf' []
where hGetLineBuf' xss = do
(eol, xss) <- catch
( do
mayBlockRead' "hGetLine" h
(\fo -> readLine fo)
(\fo bytes -> do
buf <- getBufStart fo bytes
eol <- readCharOffPtr buf (bytes-1)
xs <- if (eol == '\n')
then stToIO (unpackNBytesST buf (bytes-1))
else stToIO (unpackNBytesST buf bytes)
return (eol, xs:xss)
)
)
(\e -> if isEOFError e && not (null xss)
then return ('\n', xss)
else ioError e)
if (eol == '\n')
then return (concat (reverse xss))
else hGetLineBuf' xss
hGetLineUnBuffered :: Handle -> IO String
hGetLineUnBuffered h = do
c <- hGetChar h
if c == '\n' then
return ""
else do
l <- getRest
return (c:l)
where
getRest = do
c <-
catch
(hGetChar h)
(\ err -> do
if isEOFError err then
return '\n'
else
ioError err)
if c == '\n' then
return ""
else do
s <- getRest
return (c:s)
readCharOffPtr (Ptr a) (I# i)
= IO $ \s -> case readCharOffAddr# a i s of { (# s,x #) -> (# s, C# x #) }
\end{code}
@hLookahead hdl@ returns the next character from handle @hdl@
without removing it from the input buffer, blocking until a
character is available.
\begin{code}
hLookAhead :: Handle -> IO Char
hLookAhead handle = do
rc <- mayBlockRead "hLookAhead" handle fileLookAhead
return (chr rc)
\end{code}
%*********************************************************
%* *
\subsection{Getting the entire contents of a handle}
%* *
%*********************************************************
@hGetContents hdl@ returns the list of characters corresponding
to the unread portion of the channel or file managed by @hdl@,
which is made semi-closed.
\begin{code}
hGetContents :: Handle -> IO String
hGetContents handle =
-- can't use wantReadableHandle here, because we want to side effect
-- the handle.
withHandle handle $ \ handle_ -> do
case haType__ handle_ of
ClosedHandle -> ioe_closedHandle "hGetContents" handle
SemiClosedHandle -> ioe_closedHandle "hGetContents" handle
AppendHandle -> ioException not_readable_error
WriteHandle -> ioException not_readable_error
_ -> do
{-
To avoid introducing an extra layer of buffering here,
we provide three lazy read methods, based on character,
line, and block buffering.
-}
let handle_' = handle_{ haType__ = SemiClosedHandle }
case (haBufferMode__ handle_) of
LineBuffering -> do
str <- unsafeInterleaveIO (lazyReadLine handle (haFO__ handle_))
return (handle_', str)
BlockBuffering _ -> do
str <- unsafeInterleaveIO (lazyReadBlock handle (haFO__ handle_))
return (handle_', str)
NoBuffering -> do
str <- unsafeInterleaveIO (lazyReadChar handle (haFO__ handle_))
return (handle_', str)
where
not_readable_error =
IOError (Just handle) IllegalOperation "hGetContents"
"handle is not open for reading" Nothing
\end{code}
Note that someone may close the semi-closed handle (or change its buffering),
so each these lazy read functions are pulled on, they have to check whether
the handle has indeed been closed.
\begin{code}
lazyReadBlock :: Handle -> FILE_OBJECT -> IO String
lazyReadLine :: Handle -> FILE_OBJECT -> IO String
lazyReadChar :: Handle -> FILE_OBJECT -> IO String
lazyReadBlock handle fo = do
buf <- getBufStart fo 0
bytes <- mayBlock fo (readBlock fo) -- ConcHask: UNSAFE, may block.
case (bytes::Int) of
-3 -> -- buffering has been turned off, use lazyReadChar instead
lazyReadChar handle fo
-2 -> return ""
-1 -> -- an error occurred, close the handle
withHandle handle $ \ handle_ -> do
closeFile (haFO__ handle_) 0{-don't bother flushing-} -- ConcHask: SAFE, won't block.
return (handle_ { haType__ = ClosedHandle }, "")
_ -> do
more <- unsafeInterleaveIO (lazyReadBlock handle fo)
stToIO (unpackNBytesAccST buf bytes more)
lazyReadLine handle fo = do
bytes <- mayBlock fo (readLine fo) -- ConcHask: UNSAFE, may block.
case (bytes::Int) of
-3 -> -- buffering has been turned off, use lazyReadChar instead
lazyReadChar handle fo
-2 -> return "" -- handle closed by someone else, stop reading.
-1 -> -- an error occurred, close the handle
withHandle handle $ \ handle_ -> do
closeFile (haFO__ handle_) 0{- don't bother flushing-} -- ConcHask: SAFE, won't block
return (handle_ { haType__ = ClosedHandle }, "")
_ -> do
more <- unsafeInterleaveIO (lazyReadLine handle fo)
buf <- getBufStart fo bytes -- ConcHask: won't block
stToIO (unpackNBytesAccST buf bytes more)
lazyReadChar handle fo = do
char <- mayBlock fo (readChar fo) -- ConcHask: UNSAFE, may block.
case (char::Int) of
-4 -> -- buffering is now block-buffered, use lazyReadBlock instead
lazyReadBlock handle fo
-3 -> -- buffering is now line-buffered, use lazyReadLine instead
lazyReadLine handle fo
-2 -> return ""
-1 -> -- error, silently close handle.
withHandle handle $ \ handle_ -> do
closeFile (haFO__ handle_) 0{-don't bother flusing-} -- ConcHask: SAFE, won't block
return (handle_{ haType__ = ClosedHandle }, "")
_ -> do
more <- unsafeInterleaveIO (lazyReadChar handle fo)
return (chr char : more)
\end{code}
%*********************************************************
%* *
\subsection{Simple output functions}
%* *
%*********************************************************
@hPutChar hdl ch@ writes the character @ch@ to the file
or channel managed by @hdl@. Characters may be buffered if
buffering is enabled for @hdl@
\begin{code}
hPutChar :: Handle -> Char -> IO ()
hPutChar handle c =
c `seq` do -- must evaluate c before grabbing the handle lock
wantWriteableHandle "hPutChar" handle $ \ handle_ -> do
let fo = haFO__ handle_
flushConnectedBuf fo
rc <- mayBlock fo (filePutc fo c) -- ConcHask: UNSAFE, may block.
if rc == 0
then return ()
else constructErrorAndFail "hPutChar"
hPutChars :: Handle -> [Char] -> IO ()
hPutChars handle [] = return ()
hPutChars handle (c:cs) = hPutChar handle c >> hPutChars handle cs
\end{code}
@hPutStr hdl s@ writes the string @s@ to the file or
channel managed by @hdl@, buffering the output if needs be.
\begin{code}
hPutStr :: Handle -> String -> IO ()
hPutStr handle str = do
buffer_mode <- wantWriteableHandle_ "hPutStr" handle
(\ handle_ -> do getBuffer handle_)
case buffer_mode of
(NoBuffering, _, _) -> do
hPutChars handle str -- v. slow, but we don't care
(LineBuffering, buf, bsz) -> do
writeLines handle buf bsz str
(BlockBuffering _, buf, bsz) -> do
writeBlocks handle buf bsz str
-- ToDo: async exceptions during writeLines & writeBlocks will cause
-- the buffer to get lost in the void. Using ByteArrays instead of
-- malloced buffers is one way around this, but we really ought to
-- be able to handle it with exception handlers/block/unblock etc.
getBuffer :: Handle__ -> IO (Handle__, (BufferMode, Ptr (), Int))
getBuffer handle_ = do
let bufs = haBuffers__ handle_
fo = haFO__ handle_
mode = haBufferMode__ handle_
sz <- getBufSize fo
case mode of
NoBuffering -> return (handle_, (mode, nullPtr, 0))
_ -> case bufs of
[] -> do buf <- malloc sz
return (handle_, (mode, buf, sz))
(b:bs) -> return (handle_{ haBuffers__ = bs }, (mode, b, sz))
freeBuffer :: Handle__ -> Ptr () -> Int -> IO Handle__
freeBuffer handle_ buf sz = do
fo_sz <- getBufSize (haFO__ handle_)
if (sz /= fo_sz)
then do { free buf; return handle_ }
else do { return handle_{ haBuffers__ = buf : haBuffers__ handle_ } }
swapBuffers :: Handle__ -> Ptr () -> Int -> IO Handle__
swapBuffers handle_ buf sz = do
let fo = haFO__ handle_
fo_buf <- getBuf fo
setBuf fo buf sz
return (handle_{ haBuffers__ = fo_buf : haBuffers__ handle_ })
-------------------------------------------------------------------------------
-- commitAndReleaseBuffer handle buf sz count flush
--
-- Write the contents of the buffer 'buf' ('sz' bytes long, containing
-- 'count' bytes of data) to handle (handle must be block or line buffered).
--
-- Implementation:
--
-- for block/line buffering,
-- 1. If there isn't room in the handle buffer, flush the handle
-- buffer.
--
-- 2. If the handle buffer is empty,
-- if flush,
-- then write buf directly to the device.
-- else swap the handle buffer with buf.
--
-- 3. If the handle buffer is non-empty, copy buf into the
-- handle buffer. Then, if flush != 0, flush
-- the buffer.
commitAndReleaseBuffer
:: Handle -- handle to commit to
-> Ptr () -> Int -- address and size (in bytes) of buffer
-> Int -- number of bytes of data in buffer
-> Bool -- flush the handle afterward?
-> IO ()
commitAndReleaseBuffer hdl@(Handle h) buf sz count flush = do
h_ <- takeMVar h
-- First deal with any possible exceptions, by freeing the buffer.
-- Async exceptions are blocked, but there are still some interruptible
-- ops below.
-- note that commit doesn't *always* free the buffer, it might
-- swap it for the current handle buffer instead. This makes things
-- a whole lot more complicated, because we can't just do
-- "finally (... free buffer ...)" here.
catchException (commit hdl h_)
(\e -> do { h_ <- freeBuffer h_ buf sz; putMVar h h_ })
where
commit hdl@(Handle h) handle_ =
checkWriteableHandle "commitAndReleaseBuffer" hdl handle_ $ do
let fo = haFO__ handle_
flushConnectedBuf fo -- ???? -SDM
getWriteableBuf fo -- flush read buf if necessary
fo_buf <- getBuf fo
fo_wptr <- getBufWPtr fo
fo_bufSize <- getBufSize fo
let ok h_ = putMVar h h_ >> return ()
-- enough room in handle buffer for the new data?
if (flush || fo_bufSize - fo_wptr <= count)
-- The <= is to be sure that we never exactly fill up the
-- buffer, which would require a flush. So if copying the
-- new data into the buffer would make the buffer full, we
-- just flush the existing buffer and the new data immediately,
-- rather than copying before flushing.
then do rc <- mayBlock fo (flushFile fo)
if (rc < 0)
then constructErrorAndFail "commitAndReleaseBuffer"
else
if (flush || sz /= fo_bufSize || count == sz)
then do rc <- write_buf fo buf count
if (rc < 0)
then constructErrorAndFail "commitAndReleaseBuffer"
else do handle_ <- freeBuffer handle_ buf sz
ok handle_
-- if: (a) we don't have to flush, and
-- (b) size(new buffer) == size(old buffer), and
-- (c) new buffer is not full,
-- we can just just swap them over...
else do handle_ <- swapBuffers handle_ buf sz
setBufWPtr fo count
ok handle_
-- not flushing, and there's enough room in the buffer:
-- just copy the data in and update bufWPtr.
else do memcpy (plusPtr fo_buf fo_wptr) buf count
setBufWPtr fo (fo_wptr + count)
handle_ <- freeBuffer handle_ buf sz
ok handle_
--------------------------------------------------------------------------------
-- commitBuffer handle buf sz count flush
--
-- Flushes 'count' bytes from the buffer 'buf' (size 'sz') to 'handle'.
-- There are several cases to consider altogether:
--
-- If flush,
-- - flush handle buffer,
-- - write out new buffer directly
--
-- else
-- - if there's enough room in the handle buffer,
-- then copy new buf into it
-- else flush handle buffer, then copy new buffer into it
--
-- Make sure that we maintain the invariant that the handle buffer is never
-- left in a full state. Several functions rely on this (eg. filePutc), so
-- if we're about to exactly fill the buffer then we make sure we do a flush
-- here (also see above in commitAndReleaseBuffer).
commitBuffer
:: Handle -- handle to commit to
-> Ptr () -> Int -- address and size (in bytes) of buffer
-> Int -- number of bytes of data in buffer
-> Bool -- flush the handle afterward?
-> IO ()
commitBuffer handle buf sz count flush = do
wantWriteableHandle "commitBuffer" handle $ \handle_ -> do
let fo = haFO__ handle_
flushConnectedBuf fo -- ???? -SDM
getWriteableBuf fo -- flush read buf if necessary
fo_buf <- getBuf fo
fo_wptr <- getBufWPtr fo
fo_bufSize <- getBufSize fo
new_wptr <- -- not enough room in handle buffer?
(if flush || (fo_bufSize - fo_wptr <= count)
then do rc <- mayBlock fo (flushFile fo)
if (rc < 0) then constructErrorAndFail "commitBuffer"
else return 0
else return fo_wptr )
if (flush || fo_bufSize <= count) -- committed buffer too large?
then do rc <- write_buf fo buf count
if (rc < 0) then constructErrorAndFail "commitBuffer"
else return ()
else do memcpy (plusPtr fo_buf new_wptr) buf count
setBufWPtr fo (new_wptr + count)
return ()
write_buf fo buf 0 = return 0
write_buf fo buf count = do
rc <- mayBlock fo (write_ fo buf count)
if (rc > 0)
then write_buf fo buf (count - rc) -- partial write
else return rc
-- a version of commitBuffer that will free the buffer if an exception is
-- received. DON'T use this if you intend to use the buffer again!
checkedCommitBuffer handle buf sz count flush
= catchException (commitBuffer handle buf sz count flush)
(\e -> do withHandle__ handle (\h_ -> freeBuffer h_ buf sz)
throw e)
foreign import "memcpy" unsafe memcpy :: Ptr () -> Ptr () -> Int -> IO ()
\end{code}
Going across the border between Haskell and C is relatively costly,
so for block writes we pack the character strings on the Haskell-side
before passing the external write routine a pointer to the buffer.
\begin{code}
#ifdef __HUGS__
#ifdef __CONCURRENT_HASKELL__
/* See comment in shoveString below for explanation */
#warning delayed update of buffer disnae work with killThread
#endif
writeLines :: Handle -> Ptr () -> Int -> String -> IO ()
writeLines handle buf bufLen s =
let
shoveString :: Int -> [Char] -> IO ()
shoveString n ls =
case ls of
[] -> commitAndReleaseBuffer handle buf buflen n False{-no need to flush-}
(x:xs) -> do
primWriteCharOffAddr buf n x
{- Flushing on buffer exhaustion or newlines (even if it isn't the last one) -}
let next_n = n + 1
if next_n == bufLen || x == '\n'
then do
checkedCommitBuffer hdl buf len next_n True{-needs flush-}
shoveString 0 xs
else
shoveString next_n xs
in
shoveString 0 s
#else /* ndef __HUGS__ */
writeLines :: Handle -> Ptr () -> Int -> String -> IO ()
writeLines hdl buf len@(I# bufLen) s =
let
shoveString :: Int# -> [Char] -> IO ()
shoveString n ls =
case ls of
[] -> commitAndReleaseBuffer hdl buf len (I# n) False{-no need to flush-}
((C# x):xs) -> do
write_char buf n x
-- Flushing on buffer exhaustion or newlines
-- (even if it isn't the last one)
let next_n = n +# 1#
if next_n ==# bufLen || x `eqChar#` '\n'#
then do
checkedCommitBuffer hdl buf len (I# next_n) True{-needs flush-}
shoveString 0# xs
else
shoveString next_n xs
in
shoveString 0# s
#endif /* ndef __HUGS__ */
#ifdef __HUGS__
writeBlocks :: Handle -> Ptr () -> Int -> String -> IO ()
writeBlocks hdl buf bufLen s =
let
shoveString :: Int -> [Char] -> IO ()
shoveString n ls =
case ls of
[] -> commitAndReleaseBuffer hdl buf len n False{-no need to flush-}
(x:xs) -> do
primWriteCharOffAddr buf n x
let next_n = n + 1
if next_n == bufLen
then do
checkedCommitBuffer hdl buf len next_n True{-needs flush-}
shoveString 0 xs
else
shoveString next_n xs
in
shoveString 0 s
#else /* ndef __HUGS__ */
writeBlocks :: Handle -> Ptr () -> Int -> String -> IO ()
writeBlocks hdl buf len@(I# bufLen) s =
let
shoveString :: Int# -> [Char] -> IO ()
shoveString n ls =
case ls of
[] -> commitAndReleaseBuffer hdl buf len (I# n) False{-no need to flush-}
((C# x):xs) -> do
write_char buf n x
let next_n = n +# 1#
if next_n ==# bufLen
then do
checkedCommitBuffer hdl buf len (I# next_n) True{-needs flush-}
shoveString 0# xs
else
shoveString next_n xs
in
shoveString 0# s
write_char :: Ptr () -> Int# -> Char# -> IO ()
write_char (Ptr buf#) n# c# =
IO $ \ s# ->
case (writeCharOffAddr# buf# n# c# s#) of s2# -> (# s2#, () #)
#endif /* ndef __HUGS__ */
\end{code}
Computation @hPrint hdl t@ writes the string representation of {\em t}
given by the @shows@ function to the file or channel managed by {\em
hdl}.
[ Seem to have disappeared from the 1.4 interface - SOF 2/97 ]
\begin{code}
hPrint :: Show a => Handle -> a -> IO ()
hPrint hdl = hPutStrLn hdl . show
\end{code}
Derived action @hPutStrLn hdl str@ writes the string \tr{str} to
the handle \tr{hdl}, adding a newline at the end.
\begin{code}
hPutStrLn :: Handle -> String -> IO ()
hPutStrLn hndl str = do
hPutStr hndl str
hPutChar hndl '\n'
\end{code}
|