lucifer
full stack coder
by lucifer@duiba
slides online
About Me
homepage: https://azl397985856.github.io/
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
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
Imperative(not mentioned)
first class functions
F of FP means mathematical function, not function of javaScript
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
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
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
var obj = ={a:1};
var obj2 = obj;
obj2.a = 2;
console.log(obj.a); // 2
console.log(obj2.a); // 2
uh...
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.
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
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);
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
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
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.
FP uses recursion instead of loops.
So FP‘s call stack would be more longer,which means more memory and more excution time.
By lucifer