Cycle.js: Get in the Loop

http://slides.com/bahmutov

A functional and reactive JavaScript framework for cleaner code

A weird and difficult JavaScript framework for driving you crazy

  • Angular1
  • Angular2
  • Ember
  • Cycle.js
  • React with Redux

Defining components in

Cycle.js is conceptually maybe the best framework currently. ... But for me it is really hard to write and understand. Too hard actually.

what is

- functional ?

- reactive ?

- Cycle.js ?

- difficult about it ?

Dr. Gleb Bahmutov PhD

Node, C++, C#,

Angular, Vue.js, React, server-side etc.

work: Kensho

event analysis and statistics for financial markets

Boston and New York

what is

- functional ?

- reactive ?

- Cycle.js ?

- difficult about it ?

How do I write great apps?

simplicity & clarity

If I can read the source code

and understand what happens

then most likely it will work

const todos = getTodos()
const updatedTodos = addNewTodo(todos, newTodo)
saveTodos(updatedTodos)

where does it get the data?

how does it save the data?

is this object modified?

const todos = getTodos()
const updatedTodos = addNewTodo(todos, newTodo)
saveTodos(updatedTodos)

what if getTodos() is called again

while the first save is still executing?

const todos = getTodos()
const updatedTodos = addNewTodo(todos, newTodo)
saveTodos(updatedTodos)

functional: A way to write your program in terms of simple functions

functions everywhere

good functions

  • small

  • simple

  • pure

functional refactoring

 

Problem: given an array of numbers, multiply each number by a constant and print the result.

var numbers = [3, 1, 7];
var constant = 2;
// 6 2 14

procedural / imperative JS

var numbers = [3, 1, 7];
var constant = 2;
var k = 0;
for(k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant);
}
// 6 2 14
  • "for" loop is a pain to debug and use

  • mixing data manipulation with printing

  • code is hard to reuse

simple

a function does one thing only

var numbers = [3, 1, 7];
var constant = 2;
function mul(a, b) {
  return a * b;
}
function print(x) {
  console.log(x);
}

multiplies two numbers

prints one argument

a function does one thing only

making new functions

var numbers = [3, 1, 7];
var constant = 2;
function mul(a, b) {
  return a * b;
}
function print(x) {
  console.log(x);
}
const mulBy = mul.bind(null, constant)
// mulBy(x) same as mul(constant, x)

partial application

composition

var numbers = [3, 1, 7];
var constant = 2;
function mul(a, b) {
  return a * b;
}
function print(x) {
  console.log(x);
}
const mulBy = mul.bind(null, constant)
// mulBy(x) same as mul(constant, x)
function mulThenPrint(x) {
  print(mulBy(x))
}

fn1

fn2

x

composition

const mulBy = mul.bind(null, constant)

// function mulThenPrint(x) {
//   print(mulBy(x))
// }
const R = require('ramda')
const mulThenPrint = R.compose(print, mulBy)

fn1

fn2

Where is data variable "x"?

point free code

function mulThenPrintOld(x) {
   print(mulBy(x))
}
const mulThenPrint = R.compose(print, mulBy)
function mulThenPrintOld(x) {
  mulThenPrint(x)
}

local variable name;

can be called anything

point free code

function mulThenPrintOld(x) {
   print(mulBy(x))
}
const mulThenPrint = R.compose(print, mulBy)
function mulThenPrintOld(dragons) {
  mulThenPrint(dragons)
}

outer function does nothing but calls inner one

point free code

function mulThenPrintOld(x) {
   print(mulBy(x))
}
const mulThenPrint = R.compose(print, mulBy)

- 1 function

- 1 variable

Refactoring

var numbers = [3, 1, 7];
var constant = 2;

reusable simple functions

complex logic

iteration

function mul(a, b) {
  return a * b;
}
function print(x) {
  console.log(x);
}
const mulBy = mul.bind(null, constant)
const mulThenPrint = R.compose(print, mulBy)
for(k = 0; k < numbers.length; k += 1) {
  mulThenPrint(numbers[k]);
}
// 6 2 14

Iteration

var numbers = [3, 1, 7]
var constant = 2
for(k = 0; k < numbers.length; k += 1) {
  mulThenPrint(numbers[k])
}
numbers.forEach(mulThenPrint)
numbers
  .map(mulBy)
  .forEach(print)

clear semantics

same result

Iteration

[3, 1, 7]
    .map(x => x * 2)
    .filter(x > 10)
    .map(x => x + 10)
    .forEach(print)
// 24
// [3, 1, 7]
    // [6, 2, 14]
    // [14]
    // [24]
    // undefined

Each call (.map .filter) returns another array

Each call takes a function as an argument

Iteration

[3, 1, 7]
    .map(x => x * 2)
    .filter(x > 10)
    .map(x => x + 10)
    .forEach(print)
// 24
// [3, 1, 7]
    // [6, 2, 14]
    // [14]
    // [24]
    // undefined

Array is the data structure

which functions make the best callbacks?

Pure functions

function mul(a, b) {
  return a * b
}
  • only uses its input arguments
  • does not modify anything outside
  • same inputs => same result every time

Pure functions

function print(x) {
  console.log(x)
}
  • only uses its input arguments
  • does not modify anything outside
  • same inputs => same result every time

not pure - console is declared outside

Pure functions

function print(x) {
  console.log(x)
}
  • only uses its input arguments
  • does not modify anything outside
  • same inputs => same result every time

not pure - leaves side effects in terminal

Methods usually make poor callbacks (use "this")

[1, 2, 3].map(x => x * 2)
  .forEach(console.log)

Error: Illegal invocation

[1, 2, 3].map(x => x * 2)
  .forEach(console.log.bind(console))
[1, 2, 3].map(double)
// [2, 4, 6]
window.postMessage(double.toString())
const recreated = eval('(' + message + ')')

Pure functions are portable

function double(x) {
  return x * 2
}
const F = R.compose(f, g, h)
F(x)

pure

pure

Pure functions are the best

Simple to write

Simple to read

Simple to test

My advice

  1. Write small simple functions

  2. compose them

  3. Progress slowly through the code base

what is

- functional ?

- reactive ?

- Cycle.js ?

- difficult about it ?

var A = 10
var B = 20
var C = A + B
// C is 30
A = -5
// C is 30
var A = 10
var B = 20
var C = A + B
// C is 30

I want C to be recomputed when A or B changes

var C = A + B

Set C to value of A + B

???

C is sum of A + B

A = -5
C = A + B
// C is 15

We need to call "C = ..." to set it to the new value

imperative

A

B

C

Every code updating A or B has to also update C

imperative

A

B

C

Ties A and B to C very closely

reactive: spreadsheet

reactive: spreadsheet

imperative

A

B

C

reactive

A

B

C

C knows that it needs to update itself when A or B changes

reactive

A

B

C

A and B emit an event;

C updates when event arrives

-5

3

What are "A" & "b"?

Event Emitters

observables

Custom code

observables

are like infinite arrays

var numbers = [1, 2, 3]
numbers.forEach(print) 
// 1, 2, 3
numbers.push(100)
// nothing happens
numbers = Rx.Observable.create(observer => {







})
numbers.subscribe(print)
  observer.onNext(1)
  observer.onNext(2)
  observer.onNext(3)
  // ... after long delay
  observer.onNext(100)
  observer.onCompleted()
// 1, 2, 3
// ...
// 100

observables are like  functions that can return multiple values

// creation
var fn = function () { ... }
var ob = Rx.Observable.create(...)
// running
fn() // or fn.call()
numbers.subscribe(...)

success, error, end

var ob = Rx.Observable.create(...)
numbers.subscribe(onNext, onError, onEnd)
// stream with error
// ----1---2---3----X

// stream with end event
// --1---2-3----|

diagrams

// ----1---2------3----|
//    .map(double) 
// ----2---4------6----|
//  .filter(x => x > 4)
// ---------------6----|

diagrams: marbles

operators

[1, 2, 3]
    .map(double)        // [2, 4, 6]
    .filter(x => x > 4) // [6]
    .forEach(print)     // 6
Rx.from([1, 2, 3])       // Observable 1
    .map(double)         // Observable 2 
    .filter(x => x > 4)  // Observable 3
    .subscribe(print)    // 6

must subscribe to cold 

stream to start the event flow!

~175 RxJS operators

Rx.from([1, 2, 3])
    .map(double)
    .buffer(3)
    .delay(5)
    .subscribe(print)    
// pause of 5 seconds
// [2, 4, 6]

stream combinators

var numbers = Rx.from([1, 2, 3])
var seconds = Rx.Observable
    .interval(1000).timeInterval()
numbers
    .zip(seconds, (number, s) => number)
    .subscribe(print)
// one second pause
// 1
// ... one second later
// 2
// .... one second later
// 3

stream combinators

var numbers = Rx.from([1, 2, 3])
var seconds = Rx.Observable
    .interval(1000).timeInterval()
numbers
    .zip(seconds, (number, s) => number)
    .subscribe(print)
// -1-2-3-|             numbers 
// ----s----s----s----  seconds
//  zip((number, s) => number)
// ----1----2----3-|

User events

var numbers = Rx.from([1, 2, 3])
var clicks = Rx.Observable.fromEvent(
    document.querySelector('#btn'), 'click')
numbers
    .zip(clicks, (number, t) => number)
    .subscribe(print)
// prints 1 number every time
// element "#btn" is clicked

User events

var numbers = Rx.from([1, 2, 3])
var clicks = Rx.Observable.fromEvent(
    document.querySelector('#btn'), 'click')
// -1-2-3-|             numbers 
// --c-------c-c------  clicks
//  zip((number, c) => number)
// --1-------2-3-|
numbers
    .zip(clicks, (number, t) => number)
    .subscribe(print)
function searchWikipedia (term) {
    return $.ajax({
        url: 'http://en.wikipedia.org/w/api.php',
        dataType: 'jsonp',
        data: {
            action: 'opensearch',
            format: 'json',
            search: term
        }
    }).promise();
}

Write autocomplete

Hard problem: need to throttle, handle out of order returned results, etc.

var keyups = Rx.Observable.fromEvent($('#input'), 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)

Autocomplete with RxJs

keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
    .subscribe(function (data) {
        // display results
    });

How?

var keyups = Rx.Observable.fromEvent($('#input'), 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)

display results

keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
    .subscribe(function (data) {
        $('#results').innerHTML = '<ul>' +
            data.map(x => `<li>${x}</li>`) +
          '</ul>'
    });

Slow if only part of data changes

var keyups = Rx.Observable.fromEvent($('#input'), 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)

use virtual dom

keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
    .subscribe(function (data) {
        var vtree = vdom.ul(data.map(vdom.li))
        vdom.patch($('#results'), vtree)
    });

the hard stuff: async data and action flow

the simple part: updating UI

reactive streams

RxJs

Bacon.js

Kefir

Most.js

xstream

your choice

widely used, fast

hipster

light

fastest

tiny, for Cycle

Kensho dashboard app

data

Kensho dashboard app

Kensho dashboard app

finished?

$http.get(...).then(...)

Kensho dashboard app

finished?

$http.get(...).then(...)

.then(...)

* times

Kensho dashboard app

stream

stream operation

* times

stream

stream

stream

RxJS for the win!

stream

stream

what is

- functional ?

- reactive ?

- Cycle.js ?

- difficult about it ?

remember pure functions?

cycle.js makes your application pure

factor out side effects

function main() {
  var keyups = Rx.Observable.fromEvent(
        $('#input'), 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)
  keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
    .subscribe(function (data) {
        var vtree = vdom.ul(data.map(vdom.li))
        vdom.patch($('#results'), vtree)
    });
}
main()

Input from DOM

Output to DOM

step 1: separate output

function main() {
  var keyups = Rx.Observable.fromEvent(
        $('#input'), 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)
  return keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
    .map(data => vdom.ul(data.map(vdom.li))
}
function run(fn) {
  fn().subscribe(function (vtree) {
    vdom.patch($('#results'), vtree)
  });
}
run(main)

Input from DOM

Output to DOM

step 2: separate input

function main(dom$) {
  var keyups = dom$('#input', 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)
  return keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
    .map(data => vdom.ul(data.map(vdom.li))
}
function run(fn) {
  var dom$ = (sel, event) 
    => Rx.Observable.fromEvent(...) }
  fn(dom$).subscribe(function (vtree) {
    vdom.patch($('#results'), vtree)
  });
}
run(main)

Input and Output

pure app

step 3: cycle events

function main(dom$) {
  var keyups = dom$('#input', 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)
  return keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
    .map(data => vdom.ul(data.map(vdom.li))
}
function run(fn) {
  var dom$ = (sel, event) 
    => Rx.Observable.fromEvent(...) }
  fn(dom$).subscribe(function (vtree) {
    vdom.patch($('#results'), vtree)
  });
}
run(main)

DOM events

cycle.js framework

pure app

cycle.js is a tiny layer for controlling side effects ON TOP OF REACTIVE STREAMS 

your app's "main" connects sources to sinks using stream operators

sources to sinks streams

function main({DOM, HTTP, WebSockets, DB}) {
  var keyups = DOM.select('#input', 'keyup')
    .map(e => e.target.value)
    .filter(text => text.length > 2)
  var results = keyups.throttle(500)
    .distinctUntilChanged()
    .flatMapLatest(searchWikipedia)
  return {
    DOM: results.map(data 
      => vdom.ul(data.map(vdom.li)),
    HTTP: // Ajax request stream,
    WebSockets: // stream output to channel,
    DB: // stream of DB updates
  }
}

drivers make sources & sinks

import {makeDOMDriver} from '@cycle/dom'
import {makeHTTPDriver} from '@cycle/http'
import {makeCookieDriver} from 'cyclejs-cookie'
import {makeTermDriver} from 'cycle-blessed'
const drivers = {
  DOM: makeDOMDriver('#app'),
  HTTP: makeHTTPDriver({baseUrl: '...'}),
  cookie: makeCookieDriver(),
  term: makeTermDriver(blessed.screen())
};
run(main, drivers)

stream lib

VDOM lib

cycle.js diversity

RxJS            most.js          xstream

snabdom       virtual-dom

counter example

function main({DOM}) {
    const decrement$ = DOM.select('.decrement')
      .events('click').map(ev => -1);

    const increment$ = 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$ };
  }

counter example

function main({DOM}) {
    const decrement$ = DOM.select('.decrement')
      .events('click').map(ev => -1);

    const increment$ = DOM.select('.increment')
      .events('click').map(ev => +1);












  }

counter example

ui to intent

//  --- click ---- click ----- 
//      .map(ev => -1)
//  --- -1 ------- -1 -------- decrement$
//  -- click ------ click --- 
//      .map(ev => +1)
//  -- 1 ---------- 1 -------- .increment
function main({DOM}) {
    const decrement$ = DOM.select('.decrement')
      .events('click').map(ev => -1);

    const increment$ = DOM.select('.increment')
      .events('click').map(ev => +1);

    const action$ = Observable.merge(decrement$, increment$);
    const count$ = action$.startWith(0).scan((x,y) => x+y);








  }

counter example

ui to intent

intent to model

//   ---- -1 ----------------------------
//   ------------ 1 ------- 1 --- 1 -----
//       .merge()
//   ---- -1 ---- 1 ------- 1 --- 1 -----
//       .scan((x, y) => x + y)
//   ---- -1 ---- 0 ------- 1 --- 2 -----
function main({DOM}) {
    const decrement$ = DOM.select('.decrement')
      .events('click').map(ev => -1);

    const increment$ = 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$ };
  }

counter example

ui to intent

intent to model

function main({DOM}) {
    const decrement$ = DOM.select('.decrement')
      .events('click').map(ev => -1);

    const increment$ = 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$ };
  }

counter example

ui to intent

intent to model

model to view

reactive Redux

what is

- functional ?

- reactive ?

- Cycle.js ?

- difficult about it ?

main runs only once

events start flowing

e

e

e

e

e

e

e

e

e

X

e

run(main, drivers)

Build Rx stream

and watch events flow

cycle forces your to think about everything that can EVER happen

everything

learning cycle.js

Cycle docs are fantastic

Cycle community is friendly and helpful

learning cycle.js

First ever CycleConf (April 2016)

André Staltz @andrestaltz

Nick Johnstone @widdnz

(not pictured) Tylor Steinberger

@TylorS167

Uses functional reactive streams to describe the data flow in application 

Side effects (DOM, HTTP, etc) are isolated via drivers

Cycle.js

Works out of the box with real time backends (WebSockets, IoT)

Cycle.js

presentation.subscribe(null, null, thankAudience)

presentation.onCompleted()

Thank you!

Cycle.js: Get in the Loop

By Gleb Bahmutov

Cycle.js: Get in the Loop

Cycle.js - functional reactive framework for pure web apps. Presented at FrontEndCamp at United Nations NYC July 2016. Links and additional resources at https://glebbahmutov.com/blog/cyclejs-frontendcamp/

  • 3,557
Loading comments...

More from Gleb Bahmutov