Functional UI

The Problem

Reasoning about state and views, unpredictable behaviour and lack of composability

Solution

Functional Programming: Avoid/abstract away state

Referential Transparency


const timesTwo = (a) => a*2;

timesTwo(2) + timesTwo(2)
//=> 8

2 * timesTwo(2)
//=> 8

4 + 4
//=> 8

Pure Functions


function pureFunction (input) {
  // no side-effect
  return input.split('').reverse().join('');
}
pureFunction()

Unpure Functions


var foo = 2;
function unpureFunction (input) {
  // side-effect
  foo += 100;
  return input.split('').reverse().join('');
}

> foo //=> 2
> unpureFunction('foo') //=> oof
> foo //=> ????

Surprisingly Unpure Functions


var obj = { foo: 'bar' };
function unpureFunction (input) {
  input.foo = 'foo';
  return input;
}

> obj.foo //=> 'bar'
> unpureFunction('foo').foo //=> 'foo'
> obj.foo //=> 'foo' <-- OOPS

Tight Coupling of Functions


var obj = { foo: 'bar' };
const coupledOne = (input) => input.foo = 'foo';
const coupledTwo = (input) =>
  setTimeout(_ => console.log(input));

> coupledTwo(obj) //=> 'foo' !!!!!
> coupledOne(obj) //=> 'foo'

Predictability


const timesTwo = (a) => a*2;
> timesTwo(2) //=> 4
> timesTwo(2) //=> 4
> timesTwo(2) //=> 4
> timesTwo(2) //=> 4
> timesTwo(2) //=> 4

... gives testability


const timesTwo = (a) => a*2;
expect(timesTwo(1)).to.equal(2)
expect(timesTwo(2)).to.equal(4)
expect(timesTwo(3)).to.equal(6)
expect(timesTwo(-9999)).to.equal(-19998)

The Same Applies to UI/Components


const myComp = component(
  input => 

{input}

); myComp('Hank Pym') //=>

Hank Pym

myComp('Sam Wilson') //=>

Sam Wilson

The DOM is a side-effekt and a blob of state


dom('#foo').innerHTML = 'bar'
const coupledOne = (input) =>
  input.innerText = 'foo';

const coupledTwo = (input) =>
  setTimeout(_ =>
    console.log(input.innerText));

> coupledTwo(dom('#foo')) //=> 'foo' !!!!!
> coupledOne(dom('#foo')) //=> 'foo'

Treat DOM as an integration point


const myComp = component(
  i => 

{i}

); const myCompTwo = component( i =>

{myComp(i)}

); const output = myComp('Hank Pym'); const newOutput = output + myComp('Ant-Man'); // Persist to somewhere domUpdate(newOutput);

Allows for Compositions


const myComp = component(
  i => 

{i}

); const myCompTwo = component( i =>
{myComp(i)}
); const output = myCompTwo('Hank Pym');

Building Complexity by Composing


const listItem = component(
  i => 
  • {i}
  • ); const output = [ 'Wade', 'Hank', 'Cable' ].map(listItem); // output is now list of names
    
    const h1 = component(
      i => 

    {i}

    ); const em = component( i => {i} ); const italicH1 = compose(h1, em); var output = italicH1('Wade Wilson');
    
    function compose (...fns) {
      return (...args) =>
        fns.reduceRight((child, fn) =>
          fn.apply(this,
            child ? args.concat(child) : args),
          null);
    };
    

    Derivate Functions/Components

    
    const comp = component(
      ({children, title}) => 

    {title}

    {children

    ); const prefilledH1 = partial(comp, { title: 'Always with title' }); var output = prefilledH1('And now with body');

    What next?

    
    maybe(myComponent);
    memoize(myComponent);
    decorator(myComponent);
    compose(decorator1, decorator2, myComponent);
    

    ...or even?

    
    Bacon
      .fromEventTarget(el, 'click')
      .throttle(400)
      .map('Some Text')
      .map(myComponent) // !!!!
      .onValue(render);
    

    Thanks!

    omniscientjs/omniscient