A glimpse into
a simple FRP micro-framework
Files | Scripting |
---|---|
static content served | static content served |
dynamic server content | |
AJAX | Sockets |
---|---|
static content served | static content served |
dynamic server content | dynamic server content |
dynamic Data | dynamic Data |
interactive UI |
-- time --> (Browserzoic) today
(and genies don't go back in bottles)
Successive genies have been let out of the bottle
early web (age of the server)
how we got here
Client side coding has to interpret:
Coding this stuff could get messy!
maybe MVC or WebComponents will help.
Add in some services, filters, directives, routers and more... (is this helping?)
To all this, Cycle.js says "Try FRP (functional reactive programming) with streams."
how we got here
Of course there is nothing new here. Cycle.js makes web apps and there are no big reveals here. The output of this code is just "another web app."
Cycle.js gives a complete new perspective on developing UI interaction and makes you take a new approach to coding up just
"another web app."
Powered by streams, functional programming and a simple architecture that isolates all side effects. Down the rabbit hole we go.
evolution of callbacks, promises to streams
Yea, but callbacks have some problems when you try to make a layered solution. It's difficult to augment a callback with preprocessors. Callbacks will run in the scope (closure) that the function was declared in.
Promises allow many preprocessor to each run in the scope that you want.
Nice, but promises are a one shot deal. They handle a request to a server or a long running calculation and then they are done.
What if you have many promises overlapping (as in "autocomplete")? We need a way to coordinate a sequence of values.
evolution of callbacks, promises to streams
with lots of useful functions to manage them
A Stream of values over time.
You can subscribe to a stream and react to changes, you can filter, map, reduce and scan or merge them.
also called Observables
evolution of callbacks, promises to streams
Pure functions must:
// no chance this is pure
void main() { ... }
// either it has side
// effects or it does
// nothing
// better, but not pure
int main(args) { ... }
// returning an error code
// is not the product
// has a chance of being pure.
stdout main(stdin) { ... }
functional programming
functional programming
functional programming
"Smart" objects
Pass data around
Imperative programming changes values as soon as possible, writing to small parts of an application's state as needed.
Objects manage this in a controlled way through setter functions etc.
Functional programming only mutates the application state after a complete (well formed) state is calculated.
Functions never make assignments outside of their scope.
So, No Side Effects!
writing to the app's own internal data
mutating state
Side Effects have to happen or your app would never change state (i.e. never do anything).
Assignment make the imperative world go around.
Pure Functions have no side effects, Right!
But wait, without side effects, applications could not exist (at least not as we know them)!
Some place in an FRP app there has to be some side effects, but where?
mutating state
calculate a new state
arguments contain everything needed
local assignments
the result is transactional - It either succeeds, or it fails and no state is left halfway updated (no crashing in the case of Elm).
mutating state
f(x)
Well that is the ideal. Some FRP frameworks only encourage this, while others insist on it and don't allow any mutation at all.
mutating state
putting a cycle.js app together
import {div} from '@cycle/dom'
import xs from 'xstream'
function main (sources) {
const vtree$ = xs.of(
div('My First Cycle.js app')
)
const sinks = {
DOM: vtree$
}
return sinks
}
putting a cycle.js app together
function main(sources) {
const decrement$ = sources.DOM
.select('.decrement').events('click').map(ev => -1);
const increment$ = sources.DOM
.select('.increment').events('click').map(ev => +1);
const action$ = Observable.merge(decrement$, increment$);
const count$ = action$.startWith(0).scan((x,y) => x+y);
const vtree$ = count$.map(count =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + count)
])
);
return { DOM: vtree$ };
}
putting a cycle.js app together
putting a cycle.js app together
import {div, button, input, p} from '@cycle/dom'
import xs from 'xstream'
export function App (sources) {
const text$ = sources.DOM.select('.atext').events('keyup')
.map(ev => ev.target.value).startWith('hi');
const toga$ = Toggle(sources, '.atoggle');
const togb$ = Toggle(sources, '.btoggle');
const vdom$ = text$.map(text =>
div([
input('.atext'),
p("test"+JSON.stringify(text))
])
);
const compose$ = xs.combine(toga$, togb$, vdom$)
.map(([Toga, Togb, VDom]) =>
div([Toga, Togb, VDom])
);
const sinks = {
DOM: compose$
}
return sinks
}
reading sources and sending out a new DOM(a text input and two checkboxes)
// Toggle is a component that takes a source and returns a sink
function Toggle(sources, id) {
const toggle$ = sources.DOM.select(id).events('change')
.map(ev => ev.target.checked)
.startWith(false);
const togglesink$ = toggle$.map(toggled =>
div([
p('hello from a toggle.'),
input(id, {attrs: {type: 'checkbox'}}), 'Toggle '+id,
p(toggled ? 'ON' : 'off')
])
);
return togglesink$;
}
wrapping up with the goods & some bads
wrapping up with the goods & some bads
If there is time we could grok this slightly more involved to-do-list example.