Cycle.js

A glimpse into

a simple FRP micro-framework

Cycle.js Agenda

  • how we got here
    (where are we and why is this even a thing)
  • evolution of callbacks, promises to streams (or observables)
  • functional programming (purity)
  • mutating state (can you localize it)
  • putting a (simple) cycle.js app together
  • wrap up w/ the goods & some bads

Web site complexity is not going away.

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

Browser Concerns

Client side coding has to interpret:  

  1. Intention of the user (via incoming events)
  2. Update the model (according to some business logic)
  3. Render a view (change the DOM quickly

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

Don't see anything new 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."

This is completely different​

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."

Is Cycle.js Different?

Powered by streams, functional programming and a simple architecture that isolates all side effects. Down the rabbit hole we go.

Callbacks

non-blocking for async

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

chaining data processors

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

streams

like an array of values that never ends

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

Roll the marbles.

 

 

evolution of callbacks, promises to streams

pure functions

Pure functions must:

  • only access their arguments
    (no reaching out of scope. Not even using "this.") referential transparency
  • return a value
// 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

pure benefits

  • testable, dependable (given the same input they always produce the same output)
  • mapping, composing, recursive and other higher order functionality
  • properties of translation on function can be used associative commutative ... 
  • no dependency on scope
  • efficient in parallel processing environments

functional programming

functional programming

Functional

"Dumb" objects

Pass logic around

 

@bahmutov

OO

"Smart" objects

Pass data around

Imperative

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

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!

ways of managing state

writing to the app's own internal data

mutating state

Side Effects

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 FRP

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?

But wait, you have to have side effects

mutating state

The side effects are out there

FRP makes a new state without partially setting the state.

calculate a new state

  • arguments contain everything needed
  • local assignments
  • returns result of calculation

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)

FRP frameworks collect all side effects in one place

Well that is the ideal. Some FRP frameworks only encourage this, while others insist on it and don't allow any mutation at all.

  • Redux, suggests it
  • HyperApp.js encourages
  • Cycle.js encourages and makes it easy to conform.
  • Elm-lang insist on it!

 

mutating state

sinks = main(sources) {

    //your code here, keep it pure.

}

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

  • new tools are being created to allow you to see the stream structure in your app, they reads like a specification of the app https://github.com/cyclejs/cyclejs/tree/master/devtool
  • Streams are re-playable so you get great bug reports or you can use a time traveling debugger.
  • Intent, Model, View is only one way to cut the problem up. You are free to choose the best way for your app.
    for example, see: cycle-onionify and cycle-collections 
  • the core is so small and simple that it is not likely to change to much

the GOODS

wrapping up with the goods & some bads

  • cycle.js is immature
  • Streams are hard to wrap your head around (there is a learning curve)
  • Still finding a way to make common components interchangeable...

some Bads

If there is time we could grok this slightly more involved to-do-list example.

Cycle.js FRP

By Nate Morse

Cycle.js FRP

Jun 2017

  • 1,019