-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathFileIO.hs
More file actions
262 lines (230 loc) · 8.22 KB
/
FileIO.hs
File metadata and controls
262 lines (230 loc) · 8.22 KB
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
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-|
Module : Control.Runner.FileIO
Description : Runners implementing file IO
Copyright : (c) Danel Ahman, 2019
License : MIT
Maintainer : danel.ahman@eesti.ee
Stability : experimental
This module provides a variety of runners implementing file IO.
These runners mostly differ in what they store in their runtime
state, e.g., storing a file handle vs storing the accumulated
writes to a file.
-}
module Control.Runner.FileIO (
FileIO(..), fOpenOS, fCloseOS, fReadOS, fWriteOS,
File(..), fRead, fWrite, FIOState, FHState, FCState,
fioRunner, fhRunner, fcRunner,
ioFioInitialiser, fioFhInitialiser, fhFcInitialiser,
ioFioFinaliser, fioFhFinaliser, fhFcFinaliser,
withFile
) where
import Control.Runner
import System.IO hiding (withFile)
import qualified Data.ByteString.Char8 as B
-- | An effect for performing file IO.
data FileIO a where
-- | Algebraic operation for opening a file in a given mode.
OpenFile :: FilePath -> IOMode -> FileIO Handle
-- | Algebraic operation of closing a given file.
CloseFile :: Handle -> FileIO ()
-- | Algebraic operation for reading from a given file.
ReadFile :: Handle -> FileIO String
-- | Algebraic operation for writing to a given file.
WriteFile :: Handle -> String -> FileIO ()
-- | Generic effect for opening a file in a given mode.
fOpenOS :: (Member FileIO sig) => FilePath -> IOMode -> User sig Handle
fOpenOS fn mode = performU (OpenFile fn mode)
-- | Generic effect for closing a given file.
fCloseOS :: (Member FileIO sig) => Handle -> User sig ()
fCloseOS fh = performU (CloseFile fh)
-- | Generic effect for reading from a given file.
fReadOS :: (Member FileIO sig) => Handle -> User sig String
fReadOS fh = performU (ReadFile fh)
-- | Generic effect for writing to a given file.
fWriteOS :: (Member FileIO sig) => Handle -> String -> User sig ()
fWriteOS fh s = performU (WriteFile fh s)
-- | An effect for performing reads and writes (on a file whose file
-- handle is hidden by the user code through the use of runners).
--
-- In this module, we additionally suppose that Read denotes
-- reading the initial value of a file when using a runner.
data File a where
-- | Algebraic operation for reading (from a file that is hidden from user code).
Read :: File String
-- | Algebraic operation for writing (to a file that is hidden from user code).
Write :: String -> File ()
-- | Generic effect for reading (from a file that is hidden from user code).
fRead :: (Member File sig) => User sig String
fRead = performU Read
-- | Generic effect for writing (to a file that is hidden from user code).
fWrite :: (Member File sig) => String -> User sig ()
fWrite s = performU (Write s)
--
-- FIO: File-fragment of the top-level IO-container.
--
-- The state of FIO is trivial because we cannot
-- internally access nor represent the real world.
--
-- | Type of the runtime state of the runner `fioRunner`.
--
-- The state is trivial because this runner directly delegates
-- the file IO operations to Haskell's `IO` monad operations.
type FIOState = ()
-- | The co-operations of the runner `fioRunner`.
fioCoOps :: Member IO sig => FileIO a -> Kernel sig FIOState a
fioCoOps (OpenFile fn mode) =
performK (openFile fn mode)
fioCoOps (CloseFile fh) =
performK (hClose fh)
fioCoOps (ReadFile fh) =
-- using ByteString IO to ensure strictness of IO
do s <- performK (B.hGetContents fh);
return (B.unpack s)
fioCoOps (WriteFile fh s) =
performK (B.hPutStr fh (B.pack s))
-- | Runner that implements the `FileIO` effect, by delegating
-- the file IO operations to Haskell's `IO` monad operations.
--
-- Intuitively, this runner focusses on a fraction of the larger,
-- external signature (namely, that of the `IO` monad).
fioRunner :: Member IO sig => Runner '[FileIO] sig FIOState
fioRunner = mkRunner fioCoOps
-- | Type of the runtime state of the runner `fhRunner`.
--
-- The state comprises the initial contents of the file
-- and then a file handle supporting (over)writing to the file.
type FHState = (String , Handle)
-- | Type co-operations of the runner `fhRunner`.
fhCoOps :: Member FileIO sig => File a -> Kernel sig FHState a
fhCoOps Read =
do (s,fh) <- getEnv;
return s
fhCoOps (Write s') =
do (s,fh) <- getEnv;
performK (WriteFile fh s')
-- | Runner that implements the `File` effect, by
-- returning the internally stored (initial) contents
-- on `Read` operations, and delegates `Write` operations
-- to some enveloping runner for the `FileIO` effect,
-- using the file handle stored in its runtime state.
fhRunner :: Member FileIO sig => Runner '[File] sig FHState
fhRunner = mkRunner fhCoOps
--
-- FC: File-runner that operates on the contents of a single file.
--
-- The state of FC is the initial contents of the file and
-- then the contents to be written to the file in finally.
--
-- | Type of the runtime state of the runner `fcRunner`.
--
-- The state comprises the initial contents of the file,
-- and then an accumulator for strings to be written to
-- the file in the finalisation (when running with `fcRunner`).
type FCState = (String , String)
-- | The co-operations of the runner `fcRunner`.
fcCoOps :: File a -> Kernel sig FCState a
fcCoOps Read =
do (s,s') <- getEnv;
return s
fcCoOps (Write s'') =
do (s,s') <- getEnv;
setEnv (s,s' ++ s'')
-- | Runner that implements the `File` effect,
-- by returning the internally stored (initial)
-- contents on `Read` operations, and accumulates
-- any `Write` operations in its runtime state.
fcRunner :: Runner '[File] sig FCState
fcRunner = mkRunner fcCoOps
--
-- IO <-> FIO.
--
-- | Initialiser for the runner `fioRunner`
-- in the `IO` monad external context.
ioFioInitialiser :: Member IO sig => User sig FIOState
ioFioInitialiser = return ()
-- | Finaliser for the runner `fioRunner`
-- in the `IO` monad external context.
--
-- As the runtime state of the `fioRunner` is trivial,
-- the finaliser simply passes on the return value.
ioFioFinaliser :: Member IO sig => a -> FIOState -> User sig a
ioFioFinaliser x _ = return x
-- | Initialiser for the runner `fhRunner`,
-- in the `FileIO` effect external context.
--
-- It first reads the initial contents of the given
-- file and then it opens the file for writing,
-- returning the initial contents and the file handle.
fioFhInitialiser :: Member FileIO sig => FilePath -> User sig FHState
fioFhInitialiser fn =
do fh <- fOpenOS fn ReadWriteMode;
s <- fReadOS fh;
fCloseOS fh;
fh <- fOpenOS fn WriteMode;
return (s,fh)
-- | Finaliser for the runner `fhRunner`,
-- in the `FileIO` effect external context.
--
-- It closes the file given file handle, and passes
-- on the return value.
fioFhFinaliser :: Member FileIO sig => a -> FHState -> User sig a
fioFhFinaliser x (_,fh) =
do fCloseOS fh;
return x
-- | Initialiser for the runner `fcRunner`,
-- in the `File` effect external context.
--
-- It first reads the initial contents of the given
-- file, and then returns the contents and the empty
-- accumulator for `Write` operations.
fhFcInitialiser :: Member File sig => User sig FCState
fhFcInitialiser =
do s <- fRead;
return (s,"")
-- | Finaliser for the runner `fcRunner`,
-- in the `File` effect external context.
--
-- It writes the accumulated writes with `Write`
-- and passes on the return value.
fhFcFinaliser :: Member File sig => a -> FCState -> User sig a
fhFcFinaliser x (_,s) =
do fWrite s;
return x
--
-- Derived with-file construct using the
-- composite IO <-> FIO <-> FH <-> FC.
--
-- | Derived with-file construct that runs user code with
-- the `File` effect in the external context of the `IO`
-- monad.
--
-- This construct nests all the different runners implemented
-- in this module, as follows
--
-- > IO monad <-> fioRunner <-> fhRunner <-> fcRunner <-> user code
--
-- with the arrows informally denoting the various initialisers
-- and finalisers for the different runners.
withFile :: FilePath -> User '[File] a -> User '[IO] a
withFile fn m =
run
fioRunner
ioFioInitialiser
(
run
fhRunner
(fioFhInitialiser fn)
(
run
fcRunner
fhFcInitialiser
m
fhFcFinaliser
)
fioFhFinaliser
)
ioFioFinaliser