FP in JavaScript

by lucifer@duiba

slides online

About Me

homepage: https://azl397985856.github.io/

Agenda

  • what is it ?
  • terminology
  • examples
  • further more
  • pros & cons
  • next

appetizer

why FP

and why JavaScript

FP become more and more popular.

Many large application codebase and popular framework such as Elm , redux, ramda and react  have adopted to FP in the past 3 years.

JavaScript adopted Scheme and Awk ,take function as first class citizen.

 

HOF and closure make it pretty suitable for FP.

 

So JavaScript will be our means of learning a paradigm

what is it ?

Functional programming is a programming paradigm

FP is the process of building software by composing pure functions, avoiding shared state, mutable data, and side-effects. 

FP focus on process, OOP focus on data itself.

But functional code can also seem a lot more dense, and the related literature can be impenetrable to newcomers

Terminology

take a breath

  • Pure functions
  • Function composition
  • curry
  • Avoid mutating state
  • Avoid side effects(not mentioned)
  • Declarative(not mentioned)
  • Imperative(not mentioned)

Pure Function

first class functions

F of FP means mathematical function, not function of javaScript

  • Given the same inputs, always returns the same output, and
  • Has no side-effects

what does it make ? - see referential transparency(discus later) 

// pure function
const add10 = (a) => a + 10
// impure function due to external non-constants
let x = 10
const addx = (a) => a + x
// also impure due to side-effect
const setx = (v) => x = v 

Function Composition

  • Function composition is the process of combining two or more functions in order to produce a new function or perform some computation

what does it make ? - fully reuse your code 

const add1 = (a) => a + 1
const times2 = (a) => a * 2
const compose = (a, b) => (c) => a(b(c))
const add1OfTimes2 = compose(add1, times2)
add1OfTimes2(5) // => 11
const add1 = (a) => a + 1
const times2 = (a) => a * 2
const pipe = (a, b) => (c) => b(a(c))
const add1OfTimes2 = pipe(add1, times2)
add1OfTimes2(5) // => 12

compose

pipe

curry

You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments.

 

You can choose to call it all at once or simply feed in each argument piecemeal.

const add = x => y => x + y;
const increment = add(1);
const addTen = add(10);

increment(2); // 3
addTen(2); // 12

Avoid mutating state


var obj = ={a:1};
var obj2 = obj;
obj2.a = 2;
console.log(obj.a);  // 2
console.log(obj2.a);  // 2

uh...

FP is immutable

will create a new one , instead of mutating

So... what about the performance ?

const data = {
  to: 7,
  tea: 3,
  ted: 4,
  ten: 12,
  A: 15,
  i: 11,
  in: 5,
  inn: 9
}

immutablejs(tree + sharing)

const data = {
  to: 7,
  tea: 3,
  ted: 4,
  ten: 12,
  A: 15,
  i: 11,
  in: 5,
  inn: 9
}

tea from 3 to 14.

examples

news list

 In practice, we cheat and use side effects , but we'll keep them minimal and separate from our pure codebase.

And for the sake of simplicity and explainability, I will take pure js instead.

our html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo</title>
  </head>
  <body>
    <div id="main" class="main"></div>

    <script src="main.js"></script>
  </body>
</html>

our main.js


 const $ = require('jquery');
 const { curry, compose } = require('ramda');

// auto curried in FP language, and in JS we should curry manually

// (cb, url) => $.ajax(url, cb)
 const getJSON = curry((cb, url) => $.ajax(url, cb));
// (selector, html) => $(selector).html(html)
 const setHtml = curry((selector, html) => $(selector).html(html));

// for debugging
// (tag, x) => { console.log(tag, x); return x; }
 const trace = curry((tag, x) => {
    console.log(tag, x);
    return x;
 });


const getNewsByTitle = title => `http://news.test.com/api/v2/news?title=${title}`;

const app = compose(getJSON(trace('response')), getNewsByTitle);

// search all the news and query the news which has a title of 'hello world'
app('hello world');

our main.js

// assume we have data structure like this:
{
  items: [{
       title: 'hello world',
       author: 'lucifer',
       cover: '',
       content: '',
       publishDate: ''
      }, {
        ...
      },
      ...
 ],
}

our main.js


const prop = curry((property, object) => object[property]);

// ['url1', 'url2', 'url3']
const coverUrls = compose(map(prop('cover')), prop('items'));

const img = src => $('<img />', { src });

// [<img src="url1" />, <img src="url2" />, <img src="url3" />]
const images = compose(map(img), coverUrls);

// <div id="main"> <img src="url1"/> <img src="url1"/>  <img src="url1"/> </div>
const render = compose(setHtml('#main'), images);

const app = compose(getJSON(render), getNewsByTitle);

app('hello world')
const $ = require("jquery");
const { curry, compose, map } = require("ramda");

const { getUrlParam } = require("./utils");

import news from "../db/news.json";

const queryNews = curry((url, news) => {
  const title = getUrlParam("title", url.slice(url.indexOf("?")));

  return {
    items: news.filter(item => item.title.indexOf(title) !== -1)
  };
});
const ajax = (url, cb) =>
  setTimeout(() => cb(queryNews(url, news.items)), Math.random() * 1000);

const getJSON = curry((cb, url) => ajax(url, cb));
const setHtml = curry((selector, html) => $(selector).html(html));
const trace = curry((tag, x) => {
  console.log(tag, x);
  return x;
});

const getNewsByTitle = title =>
  `http://news.test.com/api/v2/news?title=${title}`;
const prop = curry((property, object) => object[property]);
const coverUrls = compose(
  map(prop("cover")),
  trace("items: "),
  prop("items")
);

const img = src => $("<img />", { src });

const images = compose(
  map(img),
  trace("coverUrls: "),
  coverUrls
);

const render = compose(
  setHtml("#main"),
  trace("iamges: "),
  images
);
const app = compose(
  getJSON(render),
  trace("news url: "),
  getNewsByTitle
);

app("hello world");

code can be found here: https://github.com/azl397985856/functional-programming/tree/master/examples/news

finally

trace

a little confused?

take it easy~

how does news-list work?

const coverUrls = compose(map(prop('cover')), prop('items'));

const img = src => $('<img />', { src });

const images = compose(map(img), coverUrls);

const render = compose(setHtml('#main'), images);
const app = compose(getJSON(render), getNewsByTitle);

further more

todo list with react

code can be found: 

https://github.com/azl397985856/functional-programming/tree/master/examples/todoList

const getItem = item => <li>{item}</li>;

const getWrapper = className => items => <ul className={className}>{items}</ul>;

const render = compose(getWrapper('demo-wrapper'), map(getItem))

ReactDOM.render(
    render(['todo1', 'todo2', 'todo3', 'todo4']),
    document.getElementById('main'));

simple

const store = {
  todos: ["todo1", "todo2", "todo3", "todo4"]
};


function forceRender() {
  ReactDOM.render(render(store.todos), document.getElementById("main"));
}
function deleteTodoByItem(item) {
  store.todos = store.todos.filter(todo => todo !== item);
  forceRender();
}

const getItem = onDelete => classname => item => (
  <li className={classname} key={item}>
    {item}
    <span className="delete" onClick={onDelete.bind(null, item)}>
      X
    </span>
  </li>
);

const getWrapper = className => items => <ul className={className}>{items}</ul>;

const render = compose(
  getWrapper("demo-wrapper"),
  map(getItem(deleteTodoByItem)("demo-todo-item"))
);

ReactDOM.render(render(store.todos), document.getElementById("main"));

and more

Pros & cons

Pros

  • high-level abstractions that hide a large number of details of such routine operations like iterating
  • out of the hell of side effect and mutable data.
  • faster(sometimes)

lazy evaluation

square(3 + 4)
(3 + 4) * (3 + 4) // evaluated the outermost expression
7 * 7 // both reduced at the same time due to reference sharing
49
square(3 + 4)
(3 + 4) * (3 + 4) // evaluated the outermost expression
7 * (3 + 4)
7 * 7
49

normal

Haskell( a FP language)

const results = _.chain(people)
  .pluck('lastName')
  .filter((name) => name.startsWith('Smith'))
  .take(5)
  .value()

another example

Cons

performance(sometime)

If the imperative version is tree like, the cost for lookups in the functional version is the same, but the cost for updating is a bit higher because a path needs to be copied rather than just 1 node modified in place. 

For a data structure like an unordered map, you can use a hash table backed by an array for the imperative version, but you'll have to use a tree for the purely functional one. This can cause about a 10x-20x slowdown.

Data Structures Are Antithetical to Functional Programming

  • Data.Map.Map is a balanced binary tree internally, so its time complexity for lookups is O(log n). I believe it's a "persistent" data structure, meaning it's implemented such that mutative operations yield a new copy with only the relevant parts of the structure updated.
  • Data.HashMap.Map is a Data.IntMap.IntMap internally, which in turn is implemented as Patricia tree; its time complexity for lookups is O(min(n, W)) where W is the number of bits in an integer. It is also "persistent."
  • Data.HashTable.HashTable is an actual hash table, with time complexity O(1) for lookups. However, it is a mutable data structure -- operations are done in-place -- so you're stuck in the IO monad if you want to use it.

FP uses recursion instead of  loops. 

So FP‘s call stack would be more longer,which means more memory and more excution time.

References

  • https://github.com/azl397985856/functional-programming
  • https://github.com/stoeffel/awesome-fp-js
  • https://github.com/MostlyAdequate/mostly-adequate-guide
  • https://ramdajs.com/
  • https://github.com/knowthen/elm
  • https://www.haskel.com/

next

  • container
  • error handling
  • async handling
  • monad

Thanks

Made with Slides.com