I'm trying to get to grips with Yampa, but from the definition of the higher-level signal functions like integral
it's not obvious to me how I would define such signal functions myself with Yampa's exported combinators with idiomatic Haskell (I don't care about performance at the moment). My goal is to learn how I can write my own combinators for debouncing, buffering, grouping, etc.
The integral
function is defined with the unexported constructors SF
and SF'
. How can I write it only with the other exported combinators, possibly using a more idiomatic Yampa style with switches and arrow notation?
integral :: VectorSpace a s => SF a a
integral = SF {sfTF = tf0}
where
tf0 a0 = (integralAux igrl0 a0, igrl0)
igrl0 = zeroVector
integralAux igrl a_prev = SF' tf -- True
where
tf dt a = (integralAux igrl' a, igrl')
where
igrl' = igrl ^+^ realToFrac dt *^ a_prev
The unexported constructors can be changed for exported versions by looking at identity
, constant
, arrPrim
, etc., but I found it more interesting to take the more general approach of using causal commutative arrows from the research of Hai Liu et al.
An accompanying repository shows an integral
implementation in the style I want:
integral :: ArrowInit a => a Double Double
integral = proc x -> do
rec let i' = i + x * dt
i <- init 0 -< i'
returnA -< i
For understanding it, reading Hai's papers has helped me, as they go into several examples. If we assume x
to be a stream of Doubles, then the stream i'
of integral values after reading the respective samples equals the stream of integral values before reading the respective samples plus the following input element multiplied by the time step (which is fixed in this formulation), and the stream of integral values i
is calculated for the first sample as 0 (init 0
) and for following samples from i'
. In Yampa, init
is called iPre
.