Functional Reactive Programming: Introduction to SynapseGrid
Functional programming paradigm opens new ways of building complex software systems. We wish to keep all operations side-effect-free and have all benefits of immutability. Also we want a way to compose functional building blocks into bigger systems that retain the same properties.
Traditional functional composition
Constructing a complex system from functions in a traditional way looks something like:
def f3(x) = f2(f1(x))
def f3(x) = f2(f1(x))
or
def g3(xs:List) = xs.map(f1).flatMap(f2)
def g3(xs:List) = xs.map(f1).flatMap(f2)
or
def h3(xs:List) =
for { x <- xs
ys = f1(x)
y <- ys
}
yield f2(y)
def h3(xs:List) =
for { x <- xs
ys = f1(x)
y <- ys
}
yield f2(y)
What's wrong with it? Well, not too much. This approach works pretty good in many situations. However, what to do if we want
- to split the flow of data into two chains?
- to implement an arbitrary DataFlow processing?
- to modularise the construction of processing function?
Builder / Runtime system separation
During the construction phase of a complex system we may tolerate mutable state, because the construction is usually single-threaded, but during runtime we want to avoid it as much as possible.
The steps looks as follows:
This system can be constructed with
val len = myContact.map( _.length )
and:
Input >> myContact
len >> Output
val s = sb.toStaticSystem
val fun = s.toDynamicSystem.toMapTransducer(Input, Output)The fun has type of a function and can be immediately used in other parts of the program:
println("fun(hello)="+fun("hello"))
The steps looks as follows:
- We construct a system using an advanced DSL.
- Convert the system into runtime representation.
- Run the system on some data.
- Arbitrary DataFlow graph of a system can be constructed incrementally.
- The construction can be done in separate (/nested) modules.
System construction DSL
Of course for simple cases we wish to retain the usual DSL with maps and flatMaps. But in order to have builder/runtime separation the map and flatMap methods are replaced. Now they do not immediately execute their operation but instead defer execution to runtime.This system can be constructed with
val len = myContact.map( _.length )
and:
Input >> myContact
len >> Output
Runtime system
The system is constructed within a mutable Builder and can be converted to static immutable system definition with the method toStaticSystem. The system definition can the be statically analyzed (converted to the above picture for instance). Or further converted to a simple function.val s = sb.toStaticSystem
val fun = s.toDynamicSystem.toMapTransducer(Input, Output)The fun has type of a function and can be immediately used in other parts of the program:
println("fun(hello)="+fun("hello"))
More info
More examples on GitHub.Ярлыки: Dataflow programming, Functional Reactive Programming, Scala, SynapseGrid