hsc3-lang-0.7: Help/Pattern/pattern.help.lhs
> import Sound.SC3.Lang.Pattern
* Beginning
| One goal of separating the synthesis engine and
| the language in SC Server is to make it possible
| to explore implementing in other languages the
| concepts expressed in the SuperCollider language
| and class library. (McCartney, 2000)
Patterns in supercollider language provide
a concise and expressive notation for writing
complex processes.
In a strict language the distinction between
data and process is quite clear.
In non-strict and purely functional languages
ordinary data types may be of indefinite extent.
> let ones = 1 : ones
> in take 5 ones
Since there is no mutation in haskell the
pattern and stream distinction is less
clear.
> let { a = [1,2,3] ++ a
> ; b = drop 2 (fmap negate a) }
> in take 5 (zip a b)
However, as was noted in relation to the noise
and related unit generators, a notation for
describing indeterminate structures presents
some interesting questions.
* Patterns are abstract
The type of a pattern is abstract.
> data P a
(P a) is the abstract data type of a pattern
with elements of type a.
Patterns are constructed, manipulated and destructured
using the functions provided.
* Patterns are Monoids
> class Monoid a where
> mempty :: a
> mappend :: a -> a -> a
Patterns are instances of monoid. mempty is the
empty pattern, and mappend makes a sequence of two
patterns.
> pempty :: P a
> pappend :: P a -> P a -> P a
* Patterns are Functors
> class Functor f
> where fmap :: (a -> b) -> f a -> f b
Patterns are an instance of Functor. fmap applies
a function to each element of a pattern.
> pmap :: (a -> b) -> P a -> P b
* Patterns are Applicative
> class (Functor f) => Applicative f where
> pure :: a -> f a
> (<*>) :: f (a -> b) -> f a -> f b
Patterns are instances of Applicative (McBride and
Paterson, 2007). The pure function lifts a value
into an infinite pattern of itself. The (<*>)
function applies a pattern of functions to a
pattern of values.
> ppure :: a -> P a
> papply :: P (a -> b) -> P a -> P b
Consider summing two patterns:
> import Control.Applicative
> let { p = pseq [1, 3, 5] 1
> ; q = pseq [6, 4, 2] 1 }
> in evalP 0 (pure (+) <*> p <*> q)
* Patterns are Monads
> class Monad m where
> (>>=) :: m a -> (a -> m b) -> m b
> return :: a -> m a
Patterns are an instance of the Monad class
(Wadler, 1990). The (>>=) function, pronounced
bind, is the mechanism for processing a monadic
value. The return function places a value into
the monad, for the pattern case it creates a
single element pattern.
> pbind :: P x -> (x -> P a) -> P a
> preturn :: a -> P a
The monad instance for Patterns follows the
standard monad instance for lists, for example:
> evalP 0 (pseq [1, 2] 1 >>= \x ->
> pseq [3, 4, 5] 1 >>= \y ->
> return (x, y))
which may be written using the haskell do notation
as:
> evalP 0 (do { x <- pseq [1, 2] 1
> ; y <- pseq [3, 4, 5] 1
> ; return (x, y) })
denotes the pattern having elements (1,3), (1,4),
(1,5), (2,3), (2,4) and (2,5).
* Patterns are numerical
Patterns are instances of both Num:
> class (Eq a, Show a) => Num a where
> (+) :: a -> a -> a
> (*) :: a -> a -> a
> (-) :: a -> a -> a
> negate :: a -> a
> abs :: a -> a
> signum :: a -> a
> fromInteger :: Integer -> a
and fractional:
> class (Num a) => Fractional a where
> (/) :: a -> a -> a
> recip :: a -> a
> fromRational :: Rational -> a
Summing two patterns does not require using the
applicative notation above, and the numerical
pattern (return x) can be written as the literal
'x':
> let { p = pseq [1, 3, 5] 1
> ; q = pseq [6, 4, 2] 1 }
> in evalP 0 (p + q)
The numerical instances are written using the
applicative functions pure and <*>.
* Intederminacy, Randomness
A pattern may be given by a function from
a random number generator to a duple of
a pattern and a derived random number
generator.
> prp :: (StdGen -> (P a, StdGen)) -> P a
pfix makes a pattern determinate by seeding
the random number generator for the pattern.
> type Seed = Int
> pfix :: Seed -> P a -> P a
* Accumulation, Threading
pscan is an accumulator. It provides a mechanism
for state to be threaded through a pattern. It can
be used to write a function to remove succesive
duplicates from a pattern, to count the distance
between occurences of an element in a pattern and
so on.
> pscan :: (x -> y -> (x, a)) -> (x -> a) -> x -> P y -> P a
* Continuing
pcontinue provides a mechanism to destructure a
pattern and generate a new pattern based on the
first element and the 'rest' of the pattern.
> pcontinue :: P x -> (x -> P x -> P a) -> P a
The bind instance of monad is written in relation
to pcontinue.
> pbind p f = pcontinue p (\x q -> f x `mappend` pbind q f)
pcontinue can be used to write pfilter the
basic pattern filter, ptail which discards
the front elment of a pattern, and so on.
* Destructuring, folding
A pattern has an ordinary right fold, with the
additional requirement of a seed value for the
random number generator.
> pfoldr :: Seed -> (a -> b -> b) -> b -> P a -> b
pfoldr is the primitive traversal function for
a pattern.
Right folding with the list constructor (:) and
the empty list transforms a pattern into a list.
> let p = pser [1, 2, 3] 5 + pseq [0, 10] 3
> in pfoldr 0 (:) [] p
* Extension
The haskell patterns follow the normal haskell
behavior when operating pointwise on sequences of
different length - the longer sequence is
truncated.
The haskell expression:
> zip [1, 2] [3, 4, 5]
describes a list of two elements, being (1, 3) and
(2, 4).
This differs from the ordinary supercollider
language behaviour, where the shorter sequence is
extended in a cycle, so that the expression:
| [[1, 2], [3, 4, 5]].flop
computes a list of three elements, [1, 3], [2, 4]
and [1, 5].
* References
+ C. McBride and R. Paterson. Applicative
Programming with Effects. Journal of Functional
Programming, 17(4), 2007.
+ P. Wadler. Comprehending Monads. In Conference
on Lisp and Funcional Programming, Nice, France,
June 1990. ACM.