{-
Copyright 2009-2010 Mario Blazevic
This file is part of the Streaming Component Combinators (SCC) project.
The SCC project is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
SCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with SCC. If not, see
<http://www.gnu.org/licenses/>.
-}-- | This module defines various 'Control.Concurrent.SCC.Coroutine' types that operate on-- 'Control.Concurrent.SCC.Streams.Sink' and 'Control.Concurrent.SCC.Streams.Source' values. The simplest of the bunch-- are 'Consumer' and 'Producer' types, which respectively operate on a single source or sink. A 'Transducer' has access-- both to a 'Control.Concurrent.SCC.Streams.Source' to read from and a 'Control.Concurrent.SCC.Streams.Sink' to write-- into. Finally, a 'Splitter' reads from a single source and writes all input into two sinks of the same type,-- signalling interesting input boundaries by writing into the third sink.-- {-# LANGUAGE ScopedTypeVariables, KindSignatures, RankNTypes, ExistentialQuantification,
MultiParamTypeClasses, FlexibleContexts, FlexibleInstances, FunctionalDependencies, TypeFamilies #-}moduleControl.Concurrent.SCC.Types(-- * TypesPerformer(..),OpenConsumer,Consumer(..),OpenProducer,Producer(..),OpenTransducer,Transducer(..),OpenSplitter,Splitter(..),Boundary(..),Markup(..),Parser,-- * Type classesBranching(combineBranches),-- * ConstructorsisolateConsumer,isolateProducer,isolateTransducer,isolateSplitter,oneToOneTransducer,statelessTransducer,statefulTransducer,statelessSplitter,statefulSplitter,-- * Utility functionssplitToConsumers,splitInputToConsumers,pipePS,(>|>),(<|<))whereimportControl.Monad.CoroutineimportControl.Monad.Parallel(MonadParallel(..))importControl.Concurrent.SCC.StreamsimportControl.Category(Category(..))importControl.Monad(liftM,when)importData.Maybe(maybe)typeOpenConsumermadxr=AncestorFunctorad=>Sourcemax->CoroutinedmrtypeOpenProducermadxr=AncestorFunctorad=>Sinkmax->CoroutinedmrtypeOpenTransducerma1a2dxyr=(AncestorFunctora1d,AncestorFunctora2d)=>Sourcema1x->Sinkma2y->CoroutinedmrtypeOpenSplitterma1a2a3a4dxbr=(AncestorFunctora1d,AncestorFunctora2d,AncestorFunctora3d,AncestorFunctora4d)=>Sourcema1x->Sinkma2x->Sinkma3x->Sinkma4b->Coroutinedmr-- | A coroutine that has no inputs nor outputs - and therefore may not suspend at all, which means it's not really a-- /co/routine.newtypePerformermr=Performer{perform::mr}-- | A coroutine that consumes values from a 'Control.Concurrent.SCC.Streams.Source'.newtypeConsumermxr=Consumer{consume::forallad.OpenConsumermadxr}-- | A coroutine that produces values and puts them into a 'Control.Concurrent.SCC.Streams.Sink'.newtypeProducermxr=Producer{produce::forallad.OpenProducermadxr}-- | The 'Transducer' type represents coroutines that transform a data stream. Execution of 'transduce' must continue-- consuming the given 'Control.Concurrent.SCC.Streams.Source' and feeding the 'Control.Concurrent.SCC.Streams.Sink' as-- long as there is any data in the source.newtypeTransducermxy=Transducer{transduce::foralla1a2d.OpenTransducerma1a2dxy()}-- | The 'Splitter' type represents coroutines that distribute the input stream acording to some criteria. A splitter-- should distribute only the original input data, and feed it into the sinks in the same order it has been read from-- the source. Furthermore, the input source should be entirely consumed and fed into the first two sinks. The third-- sink can be used to supply extra information at arbitrary points in the input.-- -- A splitter can be used in two ways: as a predicate to determine which portions of its input stream satisfy a certain-- property, or as a chunker to divide the input stream into chunks. In the former case, the predicate is considered-- true for exactly those parts of the input that are written to its /true/ sink. In the latter case, a chunk is a-- contiguous section of the input stream that is written exclusively to one sink, either true or false. Anything-- written to the third sink also terminates the chunk.newtypeSplittermxb=Splitter{split::foralla1a2a3a4d.OpenSplitterma1a2a3a4dxb()}-- | A 'Markup' value is produced to mark either a 'Start' and 'End' of a region of data, or an arbitrary-- 'Point' in data. A 'Point' is semantically equivalent to a 'Start' immediately followed by 'End'. The 'Content'-- constructor wraps the actual data.dataBoundaryy=Starty|Endy|Pointyderiving(Eq,Show)dataMarkupyx=Contentx|Markup(Boundaryy)deriving(Eq)typeParsermxb=Transducermx(Markupbx)instanceFunctorBoundarywherefmapf(Startb)=Start(fb)fmapf(Endb)=End(fb)fmapf(Pointb)=Point(fb)instanceFunctor(Markupy)wherefmapf(Contentx)=Content(fx)fmapf(Markupb)=Markupbinstance(Showy)=>Show(MarkupyChar)whereshowsPrecp(Contentx)s=x:sshowsPrecp(Markupb)s='[':showsb(']':s)instanceMonadm=>Category(Transducerm)whereid=Transducerpourt1.t2=isolateTransducer$\sourcesink->pipe(transducet2source)(\source->transducet1sourcesink)>>return()-- | Same as 'Control.Category.>>>' except it runs the two transducers in parallel.(>|>)::MonadParallelm=>Transducermxy->Transducermyz->Transducermxzt1>|>t2=isolateTransducer$\sourcesink->pipeP(transducet1source)(\source->transducet2sourcesink)>>return()-- | Same as 'Control.Category.<<<' except it runs the two transducers in parallel.(<|<)::MonadParallelm=>Transducermyz->Transducermxy->Transducermxzt1<|<t2=isolateTransducer$\sourcesink->pipeP(transducet2source)(\source->transducet1sourcesink)>>return()-- | Creates a proper 'Consumer' from a function that is, but can't be proven to be, an 'OpenConsumer'.isolateConsumer::forallmxr.Monadm=>(foralld.Functord=>Sourcemdx->Coroutinedmr)->ConsumermxrisolateConsumerconsume=Consumerconsume'whereconsume'::forallad.OpenConsumermadxrconsume'source=letsource'::Sourcemdxsource'=liftSourcesourceinconsumesource'-- | Creates a proper 'Producer' from a function that is, but can't be proven to be, an 'OpenProducer'.isolateProducer::forallmxr.Monadm=>(foralld.Functord=>Sinkmdx->Coroutinedmr)->ProducermxrisolateProducerproduce=Producerproduce'whereproduce'::forallad.OpenProducermadxrproduce'sink=letsink'::Sinkmdxsink'=liftSinksinkinproducesink'-- | Creates a proper 'Transducer' from a function that is, but can't be proven to be, an 'OpenTransducer'.isolateTransducer::forallmxy.Monadm=>(foralld.Functord=>Sourcemdx->Sinkmdy->Coroutinedm())->TransducermxyisolateTransducertransduce=Transducertransduce'wheretransduce'::foralla1a2d.OpenTransducerma1a2dxy()transduce'sourcesink=letsource'::Sourcemdxsource'=liftSourcesourcesink'::Sinkmdysink'=liftSinksinkintransducesource'sink'-- | Creates a proper 'Splitter' from a function that is, but can't be proven to be, an 'OpenSplitter'.isolateSplitter::forallmxb.Monadm=>(foralld.Functord=>Sourcemdx->Sinkmdx->Sinkmdx->Sinkmdb->Coroutinedm())->SplittermxbisolateSplittersplit=Splittersplit'wheresplit'::foralla1a2a3a4d.OpenSplitterma1a2a3a4dxb()split'sourcetruefalseedge=letsource'::Sourcemdxsource'=liftSourcesourcetrue'::Sinkmdxtrue'=liftSinktruefalse'::Sinkmdxfalse'=liftSinkfalseedge'::Sinkmdbedge'=liftSinkedgeinsplitsource'true'false'edge'-- | 'Branching' is a type class representing all types that can act as consumers, namely 'Consumer',-- 'Transducer', and 'Splitter'.classBranchingc(m::*->*)xr|c->mxwhere-- | 'combineBranches' is used to combine two values of 'Branch' class into one, using the given 'Consumer' binary-- combinator.combineBranches::(foralld.(Bool->(forallad'.AncestorFunctordd'=>OpenConsumermad'xr)->(forallad'.AncestorFunctordd'=>OpenConsumermad'xr)->(foralla.OpenConsumermadxr)))->Bool->c->c->cinstanceforallmxr.Monadm=>Branching(Consumermxr)mxrwherecombineBranchescombinatorparallelc1c2=Consumer$combinatorparallel(consumec1)(consumec2)instanceforallmxy.Monadm=>Branching(Transducermxy)mx()wherecombineBranchescombinatorparallelt1t2=lettransduce'::foralla1a2d.OpenTransducerma1a2dxy()transduce'sourcesink=combinatorparallel(\source->transducet1sourcesink')(\source->transducet2sourcesink')sourcewheresink'::Sinkmdysink'=liftSinksinkinTransducertransduce'instanceforallmxb.(MonadParallelm)=>Branching(Splittermxb)mx()wherecombineBranchescombinatorparallels1s2=letsplit'::foralla1a2a3a4d.OpenSplitterma1a2a3a4dxb()split'sourcetruefalseedge=combinatorparallel(\source->splits1sourcetrue'false'edge')(\source->splits2sourcetrue'false'edge')sourcewheretrue'::Sinkmdxtrue'=liftSinktruefalse'::Sinkmdxfalse'=liftSinkfalseedge'::Sinkmdbedge'=liftSinkedgeinSplittersplit'-- | Function 'oneToOneTransducer' takes a function that maps one input value to one output value each, and lifts it-- into a 'Transducer'.oneToOneTransducer::Monadm=>(x->y)->TransducermxyoneToOneTransducerf=Transducer(mapStreamf)-- | Function 'statelessTransducer' takes a function that maps one input value into a list of output values, and-- lifts it into a 'Transducer'.statelessTransducer::Monadm=>(x->[y])->TransducermxystatelessTransducerf=Transducer(\sourcesink->mapMStream_(\x->putList(fx)sink)source)-- | Function 'statefulTransducer' constructs a 'Transducer' from a state-transition function and the initial-- state. The transition function may produce arbitrary output at any transition step.statefulTransducer::Monadm=>(state->x->(state,[y]))->state->TransducermxystatefulTransducerfs0=Transducer(\sourcesink->foldMStream_(\sx->let(s',ys)=fsxinputListyssink>>returns')s0source)-- | Function 'statelessSplitter' takes a function that assigns a Boolean value to each input item and lifts it into-- a 'Splitter'.statelessSplitter::Monadm=>(x->Bool)->SplittermxbstatelessSplitterf=Splitter(\sourcetruefalseedge->partitionStreamfsourcetruefalse)-- | Function 'statefulSplitter' takes a state-converting function that also assigns a Boolean value to each input-- item and lifts it into a 'Splitter'.statefulSplitter::Monadm=>(state->x->(state,Bool))->state->Splittermx()statefulSplitterfs0=Splitter(\sourcetruefalseedge->foldMStream_(\sx->let(s',truth)=fsxin(iftruththenputtruexelseputfalsex)>>returns')s0source)-- | Given a 'Splitter', a 'Source', and three consumer functions, 'splitToConsumers' runs the splitter on the source-- and feeds the splitter's outputs to its /true/, /false/, and /edge/ sinks, respectively, to the three consumers.splitToConsumers::(Functord,Monadm,d1~SinkFunctordx,AncestorFunctora(SinkFunctor(SinkFunctord1x)b))=>Splittermxb->Sourcemax->(Sourcem(SourceFunctordx)x->Coroutine(SourceFunctordx)mr1)->(Sourcem(SourceFunctord1x)x->Coroutine(SourceFunctord1x)mr2)->(Sourcem(SourceFunctor(SinkFunctord1x)b)b->Coroutine(SourceFunctor(SinkFunctord1x)b)mr3)->Coroutinedm((),r1,r2,r3)splitToConsumersssourcetrueConsumerfalseConsumeredgeConsumer=pipe(\true->pipe(\false->pipe(splitssourcetruefalse)edgeConsumer)falseConsumer)trueConsumer>>=\(((extra,r3),r2),r1)->return(extra,r1,r2,r3)-- | Given a 'Splitter', a 'Source', and two consumer functions, 'splitInputToConsumers' runs the splitter on the source-- and feeds the splitter's /true/ and /false/ outputs, respectively, to the two consumers.splitInputToConsumers::forallmadd1xb.(MonadParallelm,d1~SinkFunctordx,AncestorFunctorad)=>Bool->Splittermxb->Sourcemax->(Sourcem(SourceFunctord1x)x->Coroutine(SourceFunctord1x)m())->(Sourcem(SourceFunctordx)x->Coroutine(SourceFunctordx)m())->Coroutinedm()splitInputToConsumersparallelssourcetrueConsumerfalseConsumer=pipePSparallel(\false->pipePSparallel(\true->splitssource'truefalse(nullSink::Sinkmdb))trueConsumer)falseConsumer>>return()wheresource'::Sourcemdxsource'=liftSourcesource