diff options
author | michaelt <what_is_it_to_do_anything@yahoo.com> | 2014-02-25 01:12:15 -0500 |
---|---|---|
committer | michaelt <what_is_it_to_do_anything@yahoo.com> | 2014-02-25 01:12:15 -0500 |
commit | 80a490ef5673cd22586215732bf8f596437e8f59 (patch) | |
tree | 7369cc571300b3196037a1d7362113ba5f516fb7 /Pipes | |
parent | 24fa37c185d329399851dd622e243e8456cb5381 (diff) | |
download | text-pipes-80a490ef5673cd22586215732bf8f596437e8f59.tar.gz text-pipes-80a490ef5673cd22586215732bf8f596437e8f59.tar.zst text-pipes-80a490ef5673cd22586215732bf8f596437e8f59.zip |
Pipes.Text documentation approaching tutorial length
Diffstat (limited to 'Pipes')
-rw-r--r-- | Pipes/Text.hs | 152 | ||||
-rw-r--r-- | Pipes/Text/IO.hs | 4 |
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 |
303 | fromLazy :: (Monad m) => TL.Text -> Producer' Text m () | 401 | fromLazy :: (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(..)) | |||
33 | import Prelude hiding (readFile, writeFile) | 33 | import 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 |