on a285b55 help sign in
streamly/src/Streamly.hs
648 lines 25 kB history raw
1-- |
2-- Module : Streamly
3-- Copyright : (c) 2017 Harendra Kumar
4--
5-- License : BSD3
6-- Maintainer : streamly@composewell.com
7-- Stability : experimental
8-- Portability : GHC
9--
10-- Streamly is a general purpose programming framework using cocnurrent data
11-- flow programming paradigm. It can be considered as a generalization of
12-- Haskell lists to monadic streaming with concurrent composition capability.
13-- The serial stream type in streamly @SerialT m a@ is like the list type @[a]@
14-- parameterized by the monad @m@. For example, @SerialT IO a@ is a moral
15-- equivalent of @[a]@ in the IO monad. Streams are constructed very much like
16-- lists, except that they use 'nil' and 'cons' instead of '[]' and ':'.
17--
18-- @
19-- > import "Streamly"
20-- > import "Streamly.Prelude" (cons, consM)
21-- > import qualified "Streamly.Prelude" as S
22-- >
23-- > S.toList $ 1 \`cons` 2 \`cons` 3 \`cons` nil
24-- [1,2,3]
25-- @
26--
27-- Unlike lists, streams can be constructed from monadic effects:
28--
29-- @
30-- > S.'toList' $ 'getLine' \`consM` 'getLine' \`consM` S.'nil'
31-- hello
32-- world
33-- ["hello","world"]
34-- @
35--
36-- Streams are processed just like lists, with list like combinators, except
37-- that they are monadic and work in a streaming fashion. Here is a simple
38-- console echo program example:
39--
40-- @
41-- > S.drain $ S.repeatM getLine & S.mapM putStrLn
42-- @
43--
44-- @SerialT Identity a@ is a moral equivalent of pure lists. Streamly utilizes
45-- fusion for high performance, therefore, we can represent and process strings
46-- as streams of 'Char', encode and decode the streams to/from UTF8 and
47-- serialize them to @Array Word8@ obviating the need for special purpose
48-- libraries like @bytestring@ and @text@.
49--
50-- For more details
51-- please see the "Streamly.Tutorial" module and the examples directory in this
52-- package.
54{-# LANGUAGE CPP #-}
55{-# LANGUAGE FlexibleContexts #-}
56{-# LANGUAGE MultiParamTypeClasses #-}
58#if __GLASGOW_HASKELL__ >= 800
59{-# OPTIONS_GHC -Wno-orphans #-}
60#endif
62#include "inline.hs"
64module Streamly
65 (
66 -- -- * Concepts Overview
67 -- -- ** Streams
68 -- -- $streams
70 -- -- ** Folds
71 -- -- $folds
73 -- -- ** Arrays
74 -- -- $arrays
76 -- * Module Overview
77 -- $streamtypes
79 -- * Type Synonyms
80 MonadAsync
82 -- * Stream transformers
83 -- | A stream represents a sequence of pure or effectful actions. The
84 -- `cons` and `consM` operations and the corresponding operators '.:' and
85 -- '|:' can be used to join pure values or effectful actions in a sequence.
86 -- The effects in the stream can be executed in many different ways
87 -- depending on the type of stream. In other words, the behavior of 'consM'
88 -- depends on the type of the stream.
89 --
90 -- There are three high level categories of streams, /spatially ordered/
91 -- streams, /speculative/ streams and /time ordered/ streams. Spatially
92 -- ordered streams, 'SerialT' and 'WSerialT', execute the effects in serial
93 -- order i.e. one at a time and present the outputs of those effects to the
94 -- consumer in the same order. Speculative streams, 'AheadT', may execute
95 -- many effects concurrently but present the outputs to the consumer in the
96 -- specified spatial order. Time ordered streams, 'AsyncT', 'WAsyncT' and
97 -- 'ParallelT', may execute many effects concurrently and present the
98 -- outputs of those effects to the consumer in time order i.e. as soon as
99 -- the output is generated.
100 --
101 -- We described above how the effects in a sequence are executed for
102 -- different types of streams. The behvavior of the 'Semigroup' and 'Monad'
103 -- instances follow the behavior of 'consM'. Stream generation operations
104 -- like 'repeatM' also execute the effects differently for different
105 -- streams, providing a concurrent generation capability when used with
106 -- stream types that execute effects concurrently. Similarly, effectful
107 -- transformation operations like 'mapM' also execute the transforming
108 -- effects differently for different types of streams.
110 -- ** Serial Streams
111 -- $serial
112 , SerialT
113 , WSerialT
115 -- ** Speculative Streams
116 -- $ahead
117 , AheadT
119 -- ** Asynchronous Streams
120 -- $async
121 , AsyncT
122 , WAsyncT
123 , ParallelT
125 -- ** Zipping Streams
126 -- $zipping
127 , ZipSerialM
128 , ZipAsyncM
130 -- * Parallel Function Application
131 -- $application
132 , (IP.|$)
133 , (IP.|&)
134 , (IP.|$.)
135 , (IP.|&.)
136 , mkAsync
138 -- * Merging Streams
139 -- $sum
140 , serial
141 , wSerial
142 , ahead
143 , async
144 , wAsync
145 , parallel
147 -- * Concurrency Control
148 -- $concurrency
149 , maxThreads
150 , maxBuffer
152 -- * Rate Limiting
153 , Rate (..)
154 , rate
155 , avgRate
156 , minRate
157 , maxRate
158 , constRate
160 -- * Stream Type Adapters
161 -- $adapters
162 , IsStream ()
164 , serially
165 , wSerially
166 , asyncly
167 , aheadly
168 , wAsyncly
169 , parallely
170 , zipSerially
171 , zipAsyncly
172 , adapt
174 -- * IO Streams
175 , Serial
176 , WSerial
177 , Ahead
178 , Async
179 , WAsync
180 , Parallel
181 , ZipSerial
182 , ZipAsync
184 -- ** Folding Containers of Streams
185 -- | These are variants of standard 'Foldable' fold functions that use a
186 -- polymorphic stream sum operation (e.g. 'async' or 'wSerial') to fold a
187 -- finite container of streams. Note that these are just special cases of
188 -- the more general 'concatMapWith' operation.
189 --
190 , IP.foldWith
191 , IP.foldMapWith
192 , IP.forEachWith
194 -- * Re-exports
195 , Semigroup (..)
197 -- * Deprecated
198 , Streaming
199 , runStream
200 , runStreaming
201 , runStreamT
202 , runInterleavedT
203 , runAsyncT
204 , runParallelT
205 , runZipStream
206 , runZipAsync
207 , StreamT
208 , InterleavedT
209 , ZipStream
210 , interleaving
211 , zipping
212 , zippingAsync
213 , (<=>)
214 , (<|)
216 {-
217 -- * Deprecated/Moved
218 -- | These APIs have been moved to other modules
219 , foldWith
220 , foldMapWith
221 , forEachWith
222 -}
223 )
224where
226import Data.Semigroup (Semigroup(..))
227import Streamly.Internal.Data.SVar (MonadAsync, Rate(..))
228import Streamly.Internal.Data.Stream.Ahead
229import Streamly.Internal.Data.Stream.Async hiding (mkAsync)
230import Streamly.Internal.Data.Stream.Combinators
231import Streamly.Internal.Data.Stream.Parallel
232import Streamly.Internal.Data.Stream.Serial
233import Streamly.Internal.Data.Stream.StreamK hiding (serial)
234import Streamly.Internal.Data.Stream.Zip
236import qualified Streamly.Prelude as P
237import qualified Streamly.Internal.Prelude as IP
238import qualified Streamly.Internal.Data.Stream.StreamK as K
239import qualified Streamly.Internal.Data.Stream.Async as Async
241-- XXX provide good succinct examples of pipelining, merging, splitting etc.
242-- below.
243--
244-- $streams
245--
246-- A program is expressed as a network of streams and folds. A stream is a
247-- source or generator of data elements and a fold is a consumer of data elements that
248-- reduces multiple input elements to a single value.
249--
250-- In the following example, a 'Word8' stream is generated by using
251-- 'Streamly.FileSystem.Handle.read' on a file handle, then the
252-- 'Streamly.Prelude.splitBySuffix' transformation splits the stream on
253-- newlines (ascii value 10); it uses the 'Streamly.Data.Fold.drain' fold to reduce
254-- the resulting lines to unit values (@()@), 'Streamly.Prelude.length' fold
255-- then counts the unit elements in the resulting stream which gives us the
256-- number of lines in the file:
257--
258-- > S.length $ S.splitOnSuffix FL.drain 10 $ FH.read fh
259--
260-- The following example folds the lines to arrays of 'Word8' using the
261-- 'Streamly.Memory.Array.writeF' fold and then wraps the lines in square
262-- brackets before writing them to standard output using
263-- 'Streamly.FileSystem.Handle.write':
264--
265-- > wrapLine ln = S.fromList "[" <> A.read ln <> S.fromList "]\n"
266-- > readLines = S.splitOnSuffix A.writeF 10
267-- > FH.write stdout $ S.concatMap wrapLine $ readLines fh1
268--
269-- One stream can be appended after another:
270--
271-- > FH.write stdout $ S.concatMap wrapLine $ readLines fh1 <> readLines fh2
272--
273-- The following example reads two files concurrently, merges the lines from
274-- the two streams and writes the resulting stream to another file:
275--
276-- > FH.write stdout $ S.concatMap wrapLine $ readLines fh1 `parallel` readLines fh2
277--
278-- There are many ways to generate, merge, zip, transform and fold data
279-- streams. Many transformations can be chained in a stream pipeline. See
280-- "Streamly.Prelude" module for combinators to manipulate streams.
282-- $folds
283--
284-- The way stream types in this module like 'SerialT' represent data sources,
285-- the same way the 'Fold' type from "Streamly.Data.Fold" represents data sinks or
286-- reducers of streams. Reducers can be combined to consume a stream source in
287-- many ways. The simplest is to reduce a stream source using a fold e.g.:
288--
289-- > S.runFold FL.length $ S.enumerateTo 100
290--
291-- Folds are consumers of streams and can be used to split a stream into
292-- multiple independent flows. Grouping transforms a stream by applying a fold
293-- on segments of a stream, distributing applies multiple folds in parallel on
294-- the same stream and combines them, partitioning sends different elements of
295-- a stream to different folds, unzipping divides the elements of a stream into
296-- parts and sends them through different folds. Parsers are nothing but a
297-- particular type of folds. Transformations can be applied contravariantly on
298-- the input of a fold.
299--
300-- @
301--
302-- |---transform----Fold m a b--------|
303-- ---stream m a-->transform-->| |---f b c ...
304-- |---transform----Fold m a c--------|
305-- | |
306-- ...
307-- @
308--
310-- $arrays
311--
312-- Streamly arrays (See "Streamly.Memory.Array") complement streams to provide an
313-- efficient computing paradigm. Streams are suitable for immutable
314-- transformations of /potentially infinite/ data using /sequential access/ and
315-- pipelined transformations whereas arrays are suitable for in-place
316-- transformations of /necessarily finite/ data using /random access/. Streams
317-- are synonymous with /sequential pipelined processing/ whereas arrays are
318-- synonymous with /efficient buffering and random access/.
319--
320-- In general, a data processing pipeline reads data from some IO device, does
321-- some processing on it and finally writes the output to another IO device.
322-- Streams provide the overall framework of sequential processing pipeline in
323-- which arrays are used as buffering elements in the middle. In addition to
324-- buffering in the middle, arrays can also be used at the boundaries of the
325-- pipeline to efficiently interface with external storage systems like memory,
326-- files and network. If streams are the pipes in a water pipeline network
327-- then arrays are like the storage tanks in the middle. On the input side,
328-- think of arrays as buckets to fetch water to feed the pipeline and on the
329-- output side buckets to remove the processed water.
330--
331-- 'ByteString' data type from the 'bytestring' package and the 'Text' data
332-- type from the 'text' package are special cases of arrays. 'ByteString' is
333-- like @Array Word8@ and 'Text' is like @utf16@ encoded @Array Word8@.
334-- Streamly arrays can be transformed as efficiently as @bytestring@ or @text@
335-- by using stream operations on them.
337-- Streams and arrays are equally important in computing. They are computing
338-- duals of each other.
340-- $streamtypes
341-- The basic stream type is 'Serial', it represents a sequence of IO actions,
342-- and is a 'Monad'. The type 'SerialT' is a monad transformer that can
343-- represent a sequence of actions in an arbitrary monad. The type 'Serial' is
344-- in fact a synonym for @SerialT IO@. There are a few more types similar to
345-- 'SerialT', all of them represent a stream and differ only in the
346-- 'Semigroup', 'Applicative' and 'Monad' compositions of the stream. 'Serial'
347-- and 'WSerial' types compose serially whereas 'Async' and 'WAsync'
348-- types compose concurrently. All these types can be freely inter-converted
349-- using type combinators without any cost. You can freely switch to any type
350-- of composition at any point in the program. When no type annotation or
351-- explicit stream type combinators are used, the default stream type is
352-- inferred as 'Serial'.
353--
354-- This module exports stream types, instances and combinators for:
355--
356-- * converting between different stream types
357-- * appending and concurrently merging streams
358-- * Concurrency control
359-- * Concurrent function application
360-- * Stream rate control
361--
362-- This module is designed to be imported unqualified:
363--
364-- @
365-- import Streamly
366-- @
367--
368-- See the "Streamly.Prelude" module for APIs for construction,
369-- generation, elimination and transformation of streams.
371------------------------------------------------------------------------------
372-- Eliminating a stream
373------------------------------------------------------------------------------
375-- | Same as 'runStream'
376--
377-- @since 0.1.0
378{-# DEPRECATED runStreaming "Please use runStream instead." #-}
379runStreaming :: (Monad m, IsStream t) => t m a -> m ()
380runStreaming = P.drain . K.adapt
382-- | Same as @runStream@.
383--
384-- @since 0.1.0
385{-# DEPRECATED runStreamT "Please use runStream instead." #-}
386runStreamT :: Monad m => SerialT m a -> m ()
387runStreamT = P.drain
389-- | Same as "Streamly.Prelude.runStream".
390--
391{-# DEPRECATED runStream "Please use Streamly.Prelude.drain instead." #-}
392runStream :: Monad m => SerialT m a -> m ()
393runStream = P.drain
395-- | Same as @runStream . wSerially@.
396--
397-- @since 0.1.0
398{-# DEPRECATED runInterleavedT "Please use 'runStream . interleaving' instead." #-}
399runInterleavedT :: Monad m => WSerialT m a -> m ()
400runInterleavedT = P.drain . K.adapt
402-- | Same as @runStream . parallely@.
403--
404-- @since 0.1.0
405{-# DEPRECATED runParallelT "Please use 'runStream . parallely' instead." #-}
406runParallelT :: Monad m => ParallelT m a -> m ()
407runParallelT = P.drain . K.adapt
409-- | Same as @runStream . asyncly@.
410--
411-- @since 0.1.0
412{-# DEPRECATED runAsyncT "Please use 'runStream . asyncly' instead." #-}
413runAsyncT :: Monad m => AsyncT m a -> m ()
414runAsyncT = P.drain . K.adapt
416-- | Same as @runStream . zipping@.
417--
418-- @since 0.1.0
419{-# DEPRECATED runZipStream "Please use 'runStream . zipSerially instead." #-}
420runZipStream :: Monad m => ZipSerialM m a -> m ()
421runZipStream = P.drain . K.adapt
423-- | Same as @runStream . zippingAsync@.
424--
425-- @since 0.1.0
426{-# DEPRECATED runZipAsync "Please use 'runStream . zipAsyncly instead." #-}
427runZipAsync :: Monad m => ZipAsyncM m a -> m ()
428runZipAsync = P.drain . K.adapt
430{-
431-- | Same as "Streamly.Prelude.foldWith".
432--
433{-# DEPRECATED foldWith "Please use Streamly.Prelude.foldWith instead." #-}
434{-# INLINABLE foldWith #-}
435foldWith :: (IsStream t, Foldable f)
436 => (t m a -> t m a -> t m a) -> f (t m a) -> t m a
437foldWith = P.foldWith
439-- | Same as "Streamly.Prelude.foldMapWith".
440--
441{-# DEPRECATED foldMapWith "Please use Streamly.Prelude.foldMapWith instead." #-}
442{-# INLINABLE foldMapWith #-}
443foldMapWith :: (IsStream t, Foldable f)
444 => (t m b -> t m b -> t m b) -> (a -> t m b) -> f a -> t m b
445foldMapWith = P.foldMapWith
447-- | Same as "Streamly.Prelude.forEachWith".
448--
449{-# DEPRECATED forEachWith "Please use Streamly.Prelude.forEachWith instead." #-}
450{-# INLINABLE forEachWith #-}
451forEachWith :: (IsStream t, Foldable f)
452 => (t m b -> t m b -> t m b) -> f a -> (a -> t m b) -> t m b
453forEachWith = P.forEachWith
454-}
456-- XXX Deprecate it in 0.8.0
457--
458-- | Make a stream asynchronous, triggers the computation and returns a stream
459-- in the underlying monad representing the output generated by the original
460-- computation. The returned action is exhaustible and must be drained once. If
461-- not drained fully we may have a thread blocked forever and once exhausted it
462-- will always return 'empty'.
463--
464-- @since 0.2.0
465{-# INLINABLE mkAsync #-}
466mkAsync :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
467mkAsync = return . Async.mkAsync
469------------------------------------------------------------------------------
470-- Documentation
471------------------------------------------------------------------------------
473-- $serial
474--
475-- When a stream consumer demands an element from a serial stream constructed
476-- as @a \`consM` b \`consM` ... nil@, the action @a@ at the head of the stream
477-- sequence is executed and the result is supplied to the consumer. When the
478-- next element is demanded, the action @b@ is executed and its result is
479-- supplied. Thus, the effects are performed and results are consumed strictly
480-- in a serial order. Serial streams can be considered as /spatially ordered/
481-- streams as the order of execution and consumption is the same as the spatial
482-- order in which the actions are composed by the programmer.
483--
484-- Serial streams enforce the side effects as well as the results of the
485-- actions to be in the same order in which the actions are added to the
486-- stream. Therefore, the semigroup operation for serial streams is not
487-- commutative:
488--
489-- @
490-- a <> b is not the same as b <> a
491-- @
492--
493-- There are two serial stream types 'SerialT' and 'WSerialT'. The stream
494-- evaluation of both the variants works in the same way as described above,
495-- they differ only in the 'Semigroup' and 'Monad' implementaitons.
497-- $ahead
498--
499-- When a stream consumer demands an element from a speculative stream
500-- constructed as @a \`consM` b \`consM` ... nil@, the action @a@ at the head
501-- of the stream is executed and the output of the action is supplied to the
502-- consumer. However, in addition to the action at the head multiple actions
503-- following it may also be executed concurrently and the results buffered.
504-- When the next element is demanded it may be served from the buffer and we
505-- may execute the next action in the sequence to keep the buffer adequately
506-- filled. Thus, the actions are executed concurrently but results consumed in
507-- serial order just like serial streams. `consM` can be used to fold an
508-- infinite lazy container of effects, as the number of concurrent executions
509-- is limited.
510--
511-- Similar to 'consM', the monadic stream generation (e.g. replicateM) and
512-- transformation operations (e.g. mapM) on speculative streams can execute
513-- multiple effects concurrently in a speculative manner.
514--
515-- How many effects can be executed concurrently and how many results can be
516-- buffered are controlled by 'maxThreads' and 'maxBuffer' combinators
517-- respectively. The actual number of concurrent threads is adjusted according
518-- to the rate at which the consumer is consuming the stream. It may even
519-- execute actions serially in a single thread if that is enough to match the
520-- consumer's speed.
521--
522-- Speculative streams enforce ordering of the results of actions in the stream
523-- but the side effects are only partially ordered. Therefore, the semigroup
524-- operation for speculative streams is not commutative from the pure outputs
525-- perspective but commutative from side effects perspective.
527-- $async
528--
529-- /Scheduling and execution:/ In an asynchronous stream @a \`consM` b \`consM`
530-- c ...@, the actions @a@, @b@, and @c@ are executed concurrently with the
531-- consumer of the stream. The actions are /scheduled/ for execution in the
532-- same order as they are specified in the stream. Multiple scheduled actions
533-- may be /executed/ concurrently in parallel threads of execution. The
534-- actions may be executed out of order and they may complete at arbitrary
535-- times. Therefore, the /effects/ of the actions may be observed out of
536-- order.
537--
538-- /Buffering:/ The /results/ from multiple threads of execution are queued in
539-- a buffer as soon as they become available. The consumer of the stream is
540-- served from this buffer. Therefore, the consumer may observe the results to
541-- be out of order. In other words, an asynchronous stream is an unordered
542-- stream i.e. order does not matter.
543--
544-- /Concurrency control:/ Threads are suspended if the `maxBuffer` limit is
545-- reached, and resumed when the consumer makes space in the buffer. The
546-- maximum number of concurrent threads depends on `maxThreads`. Number of
547-- threads is increased or decreased based on the speed of the consumer.
548--
549-- /Generation operations:/ Concurrent stream generation operations e.g.
550-- 'Streamly.Prelude.replicateM' when used in async style schedule and execute
551-- the stream generating actions in the manner described above. The generation
552-- actions run concurrently, effects and results of the actions as observed by
553-- the consumer of the stream may be out of order.
554--
555-- /Transformation operations:/ Concurrent stream transformation operations
556-- e.g. 'Streamly.Prelude.mapM', when used in async style, schedule and
557-- execute transformation actions in the manner described above. Transformation
558-- actions run concurrently, effects and results of the actions may be
559-- observed by the consumer out of order.
560--
561-- /Variants:/ There are two asynchronous stream types 'AsyncT' and 'WAsyncT'.
562-- They are identical with respect to single stream evaluation behavior. Their
563-- behaviors differ in how they combine multiple streams using 'Semigroup' or
564-- 'Monad' composition. Since the order of elements does not matter in
565-- asynchronous streams the 'Semigroup' operation is effectively commutative.
567-- $zipping
568--
569-- 'ZipSerialM' and 'ZipAsyncM', provide 'Applicative' instances for zipping the
570-- corresponding elements of two streams together. Note that these types are
571-- not monads.
573-- $application
574--
575-- Stream processing functions can be composed in a chain using function
576-- application with or without the '$' operator, or with reverse function
577-- application operator '&'. Streamly provides concurrent versions of these
578-- operators applying stream processing functions such that each stage of the
579-- stream can run in parallel. The operators start with a @|@; we can read '|$'
580-- as "@parallel dollar@" to remember that @|@ comes before '$'.
581--
582-- Imports for the code snippets below:
583--
584-- @
585-- import Streamly
586-- import qualified Streamly.Prelude as S
587-- import Control.Concurrent
588-- @
590-- $sum
591-- The 'Semigroup' operation '<>' of each stream type combines two streams in a
592-- type specific manner. This section provides polymorphic versions of '<>'
593-- which can be used to combine two streams in a predetermined way irrespective
594-- of the type.
596-- XXX An alternative design choice would be to let a control parameter affect
597-- the nearest SVar only and then it gets cleared. The benefit of the current
598-- choice is that it is simply just like global configuration, just like state
599-- behaves, so should be easy to comprehend. But it has the downside of leaking
600-- to undesired actions, that is we can forget to reset it.
601--
602-- $concurrency
603--
604-- These combinators can be used at any point in a stream composition to set
605-- parameters to control the concurrency of the /argument stream/. A control
606-- parameter set at any point remains effective for any concurrent combinators
607-- used in the argument stream until it is reset by using the combinator again.
608-- These control parameters have no effect on non-concurrent combinators in the
609-- stream, or on non-concurrent streams.
610--
611-- /Pitfall:/ Remember that 'maxBuffer' in the following example applies to
612-- 'mapM' and any other combinators that may follow it, and it does not apply
613-- to the combinators before it:
614--
615-- @
616-- ...
617-- $ maxBuffer 10
618-- $ S.mapM ...
619-- ...
620-- @
621--
622-- If we use '&' instead of '$' the situation will reverse, in the following
623-- example, 'maxBuffer' does not apply to 'mapM', it applies to combinators
624-- that come before it, because those are the arguments to 'maxBuffer':
625--
626-- @
627-- ...
628-- & maxBuffer 10
629-- & S.mapM ...
630-- ...
631-- @
633-- $adapters
634--
635-- You may want to use different stream composition styles at different points
636-- in your program. Stream types can be freely converted or adapted from one
637-- type to another. The 'IsStream' type class facilitates type conversion of
638-- one stream type to another. It is not used directly, instead the type
639-- combinators provided below are used for conversions.
640--
641-- To adapt from one monomorphic type (e.g. 'AsyncT') to another monomorphic
642-- type (e.g. 'SerialT') use the 'adapt' combinator. To give a polymorphic code
643-- a specific interpretation or to adapt a specific type to a polymorphic type
644-- use the type specific combinators e.g. 'asyncly' or 'wSerially'. You
645-- cannot adapt polymorphic code to polymorphic code, as the compiler would not know
646-- which specific type you are converting from or to. If you see a an
647-- @ambiguous type variable@ error then most likely you are using 'adapt'
648-- unnecessarily on polymorphic code.