aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authormichaelt <what_is_it_to_do_anything@yahoo.com>2014-02-25 01:12:15 -0500
committermichaelt <what_is_it_to_do_anything@yahoo.com>2014-02-25 01:12:15 -0500
commit80a490ef5673cd22586215732bf8f596437e8f59 (patch)
tree7369cc571300b3196037a1d7362113ba5f516fb7
parent24fa37c185d329399851dd622e243e8456cb5381 (diff)
downloadtext-pipes-80a490ef5673cd22586215732bf8f596437e8f59.tar.gz
text-pipes-80a490ef5673cd22586215732bf8f596437e8f59.tar.zst
text-pipes-80a490ef5673cd22586215732bf8f596437e8f59.zip
Pipes.Text documentation approaching tutorial length
-rw-r--r--Pipes/Text.hs152
-rw-r--r--Pipes/Text/IO.hs4
2 files changed, 127 insertions, 29 deletions
diff --git a/Pipes/Text.hs b/Pipes/Text.hs
index 95fc0e6..9f84429 100644
--- a/Pipes/Text.hs
+++ b/Pipes/Text.hs
@@ -135,7 +135,7 @@ import Prelude hiding (
135 135
136{- $intro 136{- $intro
137 137
138 * /Effectful Text/ 138 * /I. Effectful Text/
139 139
140 This package provides @pipes@ utilities for /text streams/, understood as 140 This package provides @pipes@ utilities for /text streams/, understood as
141 streams of 'Text' chunks. The individual chunks are uniformly /strict/, and thus you 141 streams of 'Text' chunks. The individual chunks are uniformly /strict/, and thus you
@@ -178,36 +178,103 @@ import Prelude hiding (
178 The above program will never bring more than one chunk of text (~ 32 KB) into 178 The above program will never bring more than one chunk of text (~ 32 KB) into
179 memory, no matter how long the lines are. 179 memory, no matter how long the lines are.
180 180
181 * /Lenses/ 181 * /II. Lenses/
182 182
183 As this example shows, one superficial difference from @Data.Text.Lazy@ 183 As this example shows, one superficial difference from @Data.Text.Lazy@
184 is that many of the operations, like 'lines', 184 is that many of the operations, like 'lines', are \'lensified\'; this has a
185 are \'lensified\'; this has a number of advantages (where it is possible), in particular 185 number of advantages (where it is possible); in particular it facilitates their
186 it facilitates their use with 'Parser's of Text (in the general 186 use with 'Parser's of Text (in the general <http://hackage.haskell.org/package/pipes-parse-3.0.1/docs/Pipes-Parse-Tutorial.html pipes-parse>
187 <http://hackage.haskell.org/package/pipes-parse-3.0.1/docs/Pipes-Parse-Tutorial.html pipes-parse> 187 sense.) The disadvantage, famously, is that the messages you get for type errors can be
188 sense.) 188 a little alarming. The remarks that follow in this section are for non-lens adepts.
189 Each such lens, e.g. 'lines', 'chunksOf' or 'splitAt', reduces to the 189
190 intuitively corresponding function when used with @view@ or @(^.)@. 190 Each lens exported here, e.g. 'lines', 'chunksOf' or 'splitAt', reduces to the
191 intuitively corresponding function when used with @view@ or @(^.)@. Instead of
192 writing:
193
194 > splitAt 17 producer
195
196 as we would with the Prelude or Text functions, we write
197
198 > view (splitAt 17) producer
199
200 or
201
202 > producer ^. splitAt 17
191 203
192 Note similarly that many equivalents of 'Text -> Text' functions are exported here as 'Pipe's. 204 This may seem a little indirect, but note that many equivalents of
193 They reduce to the intuitively corresponding functions when used with '(>->)'. Thus something like 205 @Text -> Text@ functions are exported here as 'Pipe's. Here too we recover the intuitively
206 corresponding functions by prefixing them with @(>->)@. Thus something like
194 207
195> stripLines = Text.unlines . Group.maps (>-> Text.stripStart) . view Text.lines 208> stripLines = Text.unlines . Group.maps (>-> Text.stripStart) . view Text.lines
196 209
197 would drop the leading white space from each line. 210 would drop the leading white space from each line.
198 211
199 The lens combinators 212 The lenses in this library are marked as /improper/; this just means that
200 you will find indispensible are @view@ / @(^.)@), @zoom@ and probably @over@. These 213 they don't admit all the operations of an ideal lens, but only "getting" and "focussing".
201 are supplied by both <http://hackage.haskell.org/package/lens lens> and 214 Just for this reason, though, the magnificent complexities of the lens libraries
215 are a distraction. The lens combinators to keep in mind, the ones that make sense for
216 our lenses, are @view@ \/ @(^.)@), @over@ \/ @(%~)@ , and @zoom@.
217
218 One need only keep in mind that if @l@ is a @Lens' a b@, then:
219
220 - @view l@ is a function @a -> b@ . Thus @view l a@ (also written @a ^. l@ )
221 is the corresponding @b@; as was said above, this function will be exactly the
222 function you think it is, given its name. Thus to uppercase the first n characters
223 of a Producer, leaving the rest the same, we could write:
224
225
226 > upper n p = do p' <- p ^. Text.splitAt n >-> Text.toUpper
227 > p'
228
229
230 - @over l@ is a function @(b -> b) -> a -> a@. Thus, given a function that modifies
231 @b@s, the lens lets us modify an @a@ by applying @f :: b -> b@ to
232 the @b@ that we can \"see\" through the lens. So @over l f :: a -> a@
233 (it can also be written @l %~ f@).
234 For any particular @a@, then, @over l f a@ or @(l %~ f) a@ is a revised @a@.
235 So above we might have written things like these:
236
237 > stripLines = Text.lines %~ maps (>-> Text.stripStart)
238 > stripLines = over Text.lines (maps (>-> Text.stripStart))
239 > upper n = Text.splitAt n %~ (>-> Text.toUpper)
240
241 - @zoom l@, finally, is a function from a @Parser b m r@
242 to a @Parser a m r@ (or more generally a @StateT (Producer b m x) m r@).
243 Its use is easiest to see with an decoding lens like 'utf8', which
244 \"sees\" a Text producer hidden inside a ByteString producer:
245 @drawChar@ is a Text parser, returning a @Maybe Char@, @zoom utf8 drawChar@ is
246 a /ByteString/ parser, returning a @Maybe Char@. @drawAll@ is a Parser that returns
247 a list of everything produced from a Producer, leaving only the return value; it would
248 usually be unreasonable to use it. But @zoom (splitAt 17) drawAll@
249 returns a list of Text chunks containing the first seventeen Chars, and returns the rest of
250 the Text Producer for further parsing. Suppose that we want, inexplicably, to
251 modify the casing of a Text Producer according to any instruction it might
252 contain at the start. Then we might write something like this:
253
254> obey :: Monad m => Producer Text m b -> Producer Text m b
255> obey p = do (ts, p') <- lift $ runStateT (zoom (Text.splitAt 8) drawAll) p
256> let seven = T.concat ts
257> case T.toUpper seven of
258> "TOUPPER" -> p' >-> Text.toUpper
259> "TOLOWER" -> p' >-> Text.toLower
260> _ -> do yield seven
261> p'
262
263 The purpose of exporting lenses is the mental economy achieved with this three-way
264 applicability. That one expression, e.g. @lines@ or @splitAt 17@ can have these
265 three uses is no more surprising than that a pipe can act as a function modifying
266 the output of a producer, namely by using @>->@ to its left: @producer >-> pipe@
267 -- but can /also/ modify the inputs to a consumer by using @>->@ to its right:
268 @pipe >-> consumer@
269
270 The three functions, @view@ \/ @(^.)@, @over@ \/ @(%~)@ and @zoom@ are supplied by
271 both <http://hackage.haskell.org/package/lens lens> and
202 <http://hackage.haskell.org/package/lens-family lens-family> The use of 'zoom' is explained 272 <http://hackage.haskell.org/package/lens-family lens-family> The use of 'zoom' is explained
203 in <http://hackage.haskell.org/package/pipes-parse-3.0.1/docs/Pipes-Parse-Tutorial.html Pipes.Parse.Tutorial> 273 in <http://hackage.haskell.org/package/pipes-parse-3.0.1/docs/Pipes-Parse-Tutorial.html Pipes.Parse.Tutorial>
204 and to some extent in the @Pipes.Text.Encoding@ module here. The use of 274 and to some extent in the @Pipes.Text.Encoding@ module here.
205 @over@ is simple, illustrated by the fact that we can rewrite @stripLines@ above as
206 275
207> stripLines = over Text.lines $ maps (>-> stripStart)
208 276
209 277 * /III. Special types:/ @Producer Text m (Producer Text m r)@ /and/ @FreeT (Producer Text m) m r@
210 * Special types: @Producer Text m (Producer Text m r)@ and @FreeT (Producer Text m) m r@
211 278
212 These simple 'lines' examples reveal a more important difference from @Data.Text.Lazy@ . 279 These simple 'lines' examples reveal a more important difference from @Data.Text.Lazy@ .
213 This is in the types that are most closely associated with our central text type, 280 This is in the types that are most closely associated with our central text type,
@@ -259,16 +326,23 @@ import Prelude hiding (
259> (Text, (Text, (Text, (Text, r)))) 326> (Text, (Text, (Text, (Text, r))))
260> ... 327> ...
261 328
262 We might also have identified the sum of those types with @Free ((,) Text) r@ 329 (We might also have identified the sum of those types with @Free ((,) Text) r@
263 -- or, more absurdly, @FreeT ((,) Text) Identity r@. Similarly, @FreeT (Producer Text m) m r@ 330 -- or, more absurdly, @FreeT ((,) Text) Identity r@.)
264 encompasses all the members of the sequence: 331
332 Similarly, our type @Texts m r@, or @FreeT (Text m) m r@ -- in fact called
333 @FreeT (Producer Text m) m r@ here -- encompasses all the members of the sequence:
265 334
266> m r 335> m r
267> Producer Text m r 336> Text m r
268> Producer Text m (Producer Text m r) 337> Text m (Text m r)
269> Producer Text m (Producer Text m (Producer Text m r)) 338> Text m (Text m (Text m r))
339> Text m (Text m (Text m (Text m r)))
270> ... 340> ...
271 341
342 We might have used a more specialized type in place of @FreeT (Producer a m) m r@,
343 or indeed of @FreeT (Producer Text m) m r@, but it is clear that the correct
344 result type of 'lines' will be isomorphic to @FreeT (Producer Text m) m r@ .
345
272 One might think that 346 One might think that
273 347
274> lines :: Monad m => Lens' (Producer Text m r) (FreeT (Producer Text m) m r) 348> lines :: Monad m => Lens' (Producer Text m r) (FreeT (Producer Text m) m r)
@@ -295,9 +369,33 @@ import Prelude hiding (
295 369
296 The @Pipes.Group@ module, which can generally be imported without qualification, 370 The @Pipes.Group@ module, which can generally be imported without qualification,
297 provides many functions for working with things of type @FreeT (Producer a m) m r@ 371 provides many functions for working with things of type @FreeT (Producer a m) m r@
372 In particular it conveniently exports the constructors for @FreeT@ and the associated
373 @FreeF@ type -- a fancy form of @Either@, namely
298 374
299 375> data FreeF f a b = Pure a | Free (f b)
300 -} 376
377 for pattern-matching. Consider the implementation of the 'words' function, or
378 of the part of the lens that takes us to the words; it is compact but exhibits many
379 of the points under discussion, including explicit handling of the @FreeT@ and @FreeF@
380 constuctors. Keep in mind that
381
382> newtype FreeT f m a = FreeT (m (FreeF f a (FreeT f m a)))
383> next :: Monad m => Producer a m r -> m (Either r (a, Producer a m r))
384
385 Thus the @do@ block after the @FreeT@ constructor is in the base monad, e.g. 'IO' or 'Identity';
386 the later subordinate block, opened by the @Free@ constructor, is in the @Producer@ monad:
387
388> words :: Monad m => Producer Text m r -> FreeT (Producer Text m) m r
389> words p = FreeT $ do -- With 'next' we will inspect p's first chunk, excluding spaces;
390> x <- next (p >-> dropWhile isSpace) -- note that 'dropWhile isSpace' is a pipe, and is thus *applied* with '>->'.
391> return $ case x of -- We use 'return' and so need something of type 'FreeF (Text m) r (Texts m r)'
392> Left r -> Pure r -- 'Left' means we got no Text chunk, but only the return value; so we are done.
393> Right (txt, p') -> Free $ do -- If we get a chunk and the rest of the producer, p', we enter the 'Producer' monad
394> p'' <- view (break isSpace) -- When we apply 'break isSpace', we get a Producer that returns a Producer;
395> (yield txt >> p') -- so here we yield everything up to the next space, and get the rest back.
396> return (words p'') -- We then carry on with the rest, which is likely to begin with space.
397
398-}
301 399
302-- | Convert a lazy 'TL.Text' into a 'Producer' of strict 'Text's 400-- | Convert a lazy 'TL.Text' into a 'Producer' of strict 'Text's
303fromLazy :: (Monad m) => TL.Text -> Producer' Text m () 401fromLazy :: (Monad m) => TL.Text -> Producer' Text m ()
diff --git a/Pipes/Text/IO.hs b/Pipes/Text/IO.hs
index 101052b..627582e 100644
--- a/Pipes/Text/IO.hs
+++ b/Pipes/Text/IO.hs
@@ -33,9 +33,9 @@ import Pipes.Safe (MonadSafe(..), Base(..))
33import Prelude hiding (readFile, writeFile) 33import Prelude hiding (readFile, writeFile)
34 34
35{- $textio 35{- $textio
36 Where pipes IO replaces lazy IO, @Producer Text m r@ replaces lazy 'Text'. 36 Where pipes @IO@ replaces lazy @IO@, @Producer Text IO r@ replaces lazy 'Text'.
37 This module exports some convenient functions for producing and consuming 37 This module exports some convenient functions for producing and consuming
38 pipes 'Text' in IO, namely, 'readFile', 'writeFile', 'fromHandle', 'toHandle', 38 pipes 'Text' in @IO@, namely, 'readFile', 'writeFile', 'fromHandle', 'toHandle',
39 'stdin' and 'stdout'. Some caveats described below. 39 'stdin' and 'stdout'. Some caveats described below.
40 40
41 The main points are as in 41 The main points are as in