moduleInternal.Signal.Segmented(-- * Segmented signal typeSSignal(SSignal),-- * Constructionconstruct,fromInitAndUpdate,-- * QuerieswithInit,updates,-- * Stateful signalsscan,-- * CapsulescrackCapsules,-- * Connectorsconsumer,producer)where-- PreludeimportPreludehiding(init)-- ControlimportControl.ApplicativeasApplicativeimportControl.ArrowasArrow-- InternalimportInternal.SignalasSignalimportInternal.Signal.Discrete.CapsuleasCapsuleimportInternal.Signal.DiscreteasDSignal(DSignal)importqualifiedInternal.Signal.DiscreteasDSignal-- FRP.GrapefruitimportFRP.Grapefruit.SetupasSetupimportFRP.Grapefruit.CircuitasCircuit-- * Segmented signal type{-|
The type of segmented signals.
A segmented signal maps times to values like a continuous signal. However, it also comprises
a set of discrete times, called /update points/. The signal can only change its value at its
update points. As a special case, the starting time of the era is always considered an
update point. So a segmented signal is composed of constant segments which are either
bounded by adjacent update points or left-bounded by a last update point and
right-unbounded. Note that value updates already take effect at the update point so that the
segments are left-closed.
It follows that a segmented signal is completely determined by the update points and the
values assigned to them. Therefore, a segmented signal can also be seen as a kind of
discrete signal with occurences at the update points. The only difference to a discrete
signal is that a segmented signal always has an occurence at the starting time of the era
whereas a discrete signal never has one.
The dual nature of segmented signals is reflected by the class instances of @SSignal@.
@SSignal@ is an instance of 'Samplee' as well as of 'Sampler'. The first means that it can
be sampled and therefore has a continuous aspect. The second means that it can be used to
sample a signal and therefore has a discrete aspect.
-}dataSSignaleraval=SSignalval(DSignaleraval){-
Reducing the signal (matching against (SSignal _ _)) forces all continous sources, the
signal depends on, to be read. Similar for reducing DSignal values (means reduction of the
map) and continous sources. Note that in the latter case, the initial value is not
necessarily reduced but initial values of other continous signals which the continous
signal’s internal SSignal depends on.
In the case of SSignal, continuous sources have to be read at the beginning. This can be
illustrated by thinking of the initial value as an occurence at starting time.
It is important that upon construction of an SSignal/CSignal via a function the SSignal and
CSignal constructors of arguments have to be reduced during reduction of the result.
Otherwise triggering of continuous source reads would not work properly.
-}instanceFunctor(SSignalera)wherefmapfun(SSignalinitupd)=SSignal(funinit)(fmapfunupd)instanceApplicative(SSignalera)wherepureval=SSignalvalDSignal.emptySSignalfunInitfunUpd<*>SSignalargInitargUpd=SSignalinit'upd'whereinit'=funInitargInitupd'=fmap(uncurry($))$DSignal.scan(funInit,argInit)(flip($))$DSignal.transUnion(first.const)(second.const)((const.).(,))funUpdargUpdinstanceSignalSSignalwhereosfSwitchsignal@(SSignalinitupd)=caseunPolyOSFinitofSSignalinit'_->SSignalinit'upd'whereupd'=initUpdateupd`DSignal.union`osfSwitch(updateSignalsignal)ssfSwitchsignalarg@(SSignal_argUpd)=ssfSwitch(fixInit<$>signal<#>arg)argUpdinitUpdate::DSignalera(PolyOSFSSignalval)->DSignaleravalinitUpdate=DSignal.crackCapsules.fmap(initCapsule.unPolyOSF)initCapsule::SSignalera'val->CapsulevalinitCapsule(SSignalinit_)=Applicative.pureinitupdateSignal::SSignalera(PolyOSFSSignalval)->SSignalera(PolyOSFDSignalval)updateSignalsignal=crackCapsules(fmapupdateCapsulesignal)updateCapsule::PolyOSFSSignalval->Capsule(PolyOSFDSignalval)updateCapsulesignal=unPolyOSFsignal`seq`Capsule(PolyOSF(updates(unPolyOSFsignal)))fixInit::PolySSFSSignalvalshape->val->PolySSFDSignalvalshapefixInitfuninit=PolySSF(unPolySSFfun.SSignalinit)instanceSamplerSSignalwheresample=sSamplesamplerMap=fmapinstanceSampleeSSignalwheredSamplefuns(SSignalargInitargUpd)=dSignal'wheredSignal'=DSignal.catMaybes$DSignal.statefulargInit$DSignal.transUnion(\funcurrentArg->(Just(funcurrentArg),currentArg))(\nextArg_->(Nothing,nextArg))(\funnextArg_->(Just(funnextArg),nextArg))funsargUpdsSample(SSignalsamplerInitsamplerUpd)signal@(SSignalinit_)=SSignalinit'upd'whereinit'=samplerInitinitupd'=samplerUpd<#>signal-- * Construction{-|
Constructs a segmented signal from an initial value and a series of updates.
A signal @construct /init/ /upd/@ has initially the value @/init/@. At each occurence in
@/upd/@, it has an update point and changes its value to the value occuring in @/upd/@. If
the segmented signal is interpreted as a kind of discrete signal, @fromInitAndUpdate@ just
adds an initial occurence of @/init/@ to the signal @/upd/@.
-}construct::val->DSignaleraval->SSignaleravalconstructvalupd=SSignalvalupd{-# DEPRECATED fromInitAndUpdate "fromInitAndUpdate is replaced by construct." #-}-- |Same as 'construct'.fromInitAndUpdate::val->DSignaleraval->SSignaleravalfromInitAndUpdatevalupd=SSignalvalupd-- * Queries-- FIXME: Is it safe to support arbitrary signal types here?{-|
Applies the second argument to the initial value of the first argument.
Using @withInit@, it is possible to create a signal which is dependent on the initial value
of a segmented signal but it is not possible to extract the initial value itself. The reason
for this restriction is that the initial value may depend on values of continuous signals
and therefore its calculation might involve doing I/O to read external continuous sources.
-}withInit::(Signalsignal)=>SSignaleraval->(val->signaleraval')->signaleraval'withInit(SSignalinit_)cont=continit-- Should be safe w.r.t. continous source fetching.{-|
Yields the sequence of updates of a segmented signal.
If the segmented signal is interpreted as a discrete signal with an additional occurence at
the start then @update@ just drops this occurence.
-}updates::SSignaleraval->DSignaleravalupdates(SSignal_upd)=upd-- * Stateful signals{-|
Accumulates the values of a discrete signal.
Applying @scan /init/ /fun/@ to a discrete signal replaces its occurence values @/val_1/@,
@/val_2/@ and so on by the values @/init/ &#x60;/fun/&#x60; /val_1/@, @(/init/
&#x60;/fun/&#x60; /val_1/) &#x60;/fun/&#x60; /val_2/@ and so on and adds an occurence of
the value @/init/@ at the beginning.
-}scan::accu->(accu->val->accu)->(DSignaleraval->SSignaleraaccu)scaninitfunupd=fromInitAndUpdateinit(DSignal.scaninitfunupd)-- * CapsulescrackCapsules::SSignalera(Capsuleval)->SSignaleravalcrackCapsules(SSignal(Capsuleinit)capUpd)=SSignalinit(DSignal.crackCapsulescapUpd)-- * Connectors{-|
Converts an event handler into a segmented signal consumer.
If a segmented signal is consumed with such a consumer, the handler is called at the
starting time of the era and at each update with the current value of the signal as its
argument. If the segmented signal is seen as a discrete signal with an additional occurence
at the start then @consumer@ behaves analogous to the 'DSignal.consumer' function of
"FRP.Grapefruit.Signal.Discrete".
-}consumer::(val->IO())->ConsumerSSignalvalconsumerhandler=Consumer$proc(SSignalinitupd)->doputSetup-<Setup.fromIO$handlerinitconsume(DSignal.consumerhandler)-<upd-- FIXME: Simplify the other consumer and producer docs by documenting function arguments.{-|
Converts a value read action and a change event handler registration into a segmented signal
producer.
-}producer::IOval-- ^an action reading the current value of the signal->(IO()->Setup)-- ^ an action which registers a given event handler so that it is called everytime-- the value of the signal has changed->ProducerSSignalvalproducerreadValchangeReg=Producer$proc_->doinit<-act-<readValupd<-produce(DSignal.producerupdReg)-<()returnA-<SSignalinitupdwhereupdReghandler=changeReg(readVal>>=handler)