What is Function Programming
函数式编程是一种编程范式。
常见编程范式:
- 面向过程/命令式
- 面向对象
- 类与实例,模型的抽象。每个实例都会有属于自己的context
- The problem with object-oriented languages is they’ve got all this implicit envieronment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and entire jungle. —— Joe Armstrong
- 函数式
- 约束 - 逻辑编程(Prolog)
- …
Pure Function 纯函数
- 没有副作用(side effect)
- 副作用:除了将一个值返回(return)给操作的调用者之外,函数还会产生其他的可观察的影响
- 常见的副作用:操作数据库、网络请求(非幂等)、DOM操作、调用其他副作用函数、给外部变量赋值
- 返回结果完全由输入决定,与外部状态无关
- 外部依赖:读外部数据、闭包会隐式地产生状态
- 引用透明:输出完全由输入决定
- 并发安全:避免 race condition
- 纯函数互相调用组装起来的函数,还是纯函数
- 易读,易懂,易于测试,易于复制和重构。
How to Purify a Function
Dependency Injection 依赖注入
function ProductPage({productId}) {
const [product, setProduct] = useState(null);
const url = 'https://myapi/product/';
async function fetchProduct() {
const response = await fetch(url + productId);
const json = await response.json();
setProduct(json)
}
useEffect(() => {
fetchProduct();
}, [])
}
将fetchProduct
抽离成纯函数:
const url = 'https://myapi/product/';
async function fetchProduct(productId, setProduct) {
const response = await fetch(url + productId);
const json = await response.json();
setProduct(json);
}
function ProductPage({productId}) {
const [product, setProduct] = useState(null);
useEffect(() => {
fetchProduct(productId, setProduct);
}, [])
}
Lazy Evaluation 惰性求值
const fs = require('fs');
const convertArticle = () => {
// this is a side effect
const articleContent = fs.readFileSync('article.txt', { encoding: 'utf-8' });
const articleLine = articleContent.split('\n');
const articleLinesWithBr = articleLine.map(line => '<p>' + line + '</p>');
const articleLinesWithTitle = ['<h1>The Article</h1>', ...articleLinesWithBr];
const articleHTML = articleLinesWithTitle.join('');
return articleHTML;
}
// use
const html = convertArticle()
先使用依赖注入方法,抽离成多个函数:
const fs = require('fs');
const getContent = () => fs.readFileSync('article.txt', { encoding: 'utf-8' });
const getLine = (articleContent) => articleContent.split('\n');
const getLinesWithBr = (articleLine) => articleLine.map(line => '<p>' + line + '</p>');
const getLinesWithTitle = (articleLinesWithBr) => ['<h1>The Article</h1>', ...articleLinesWithBr];
const getHTML = (articleLinesWithTitle) => articleLinesWithTitle.join('');
const convertArticle = () => {
// this is a side effect
const articleContent = getContent();
const articleLine = getLine(articleContent);
const articleLinesWithBr = getLinesWithBr(articleLine);
const articleLinesWithTitle = getLinesWithTitle(articleLinesWithBr);
const articleHTML = getHTML(articleLinesWithTitle);
return articleHTML;
}
// use
const html = convertArticle()
然后使用惰性求值,将convertArticle
变成一个纯函数,因此它也将返回一个函数,而不是字符串。
const fs = require('fs');
const getContent = () => fs.readFileSync('article.txt', { encoding: 'utf-8' });
const getLine = (articleContent) => articleContent.split('\n');
const getLinesWithBr = (articleLine) => articleLine.map(line => '<p>' + line + '</p>');
const getLinesWithTitle = (articleLinesWithBr) => ['<h1>The Article</h1>', ...articleLinesWithBr];
const getHTML = (articleLinesWithTitle) => articleLinesWithTitle.join('');
const convertArticle = () => {
// this is a side effect
const articleContent = () => getContent();
const articleLine = () => getLine(articleContent());
const articleLinesWithBr = () => getLinesWithBr(articleLine());
const articleLinesWithTitle = () => getLinesWithTitle(articleLinesWithBr());
const articleHTML = () => getHTML(articleLinesWithTitle());
return articleHTML;
}
// use
const html = convertArticle()()
Functor, Applicative, Monads
我们可以通过不同的方法,来优化上面的convertArticle
函数。
// raw
const convertArticle = () => {
// this is a side effect
const articleContent = () => getContent();
const articleLine = () => getLine(articleContent());
const articleLinesWithBr = () => getLinesWithBr(articleLine());
const articleLinesWithTitle = () => getLinesWithTitle(articleLinesWithBr());
const articleHTML = () => getHTML(articleLinesWithTitle());
return articleHTML;
}
const tinyConvert = () =>
() => getHTML(getLinesWithTitle(getLinesWithBr(getLine(getContent()))));
const arrayConvert = () =>
() => Array.of(getContent()) // [getContent()]
.map(getLine)
.map(getLinesWithBr)
.map(getLinesWithTitle)
.map(getHTML)
.join('')
const promiseConvert = async () =>
async () => await Promise.resolve(getContent())
.then(getLine)
.then(getLinesWithBr)
.then(getLinesWithTitle)
.then(getHTML)
How to Optimize a React Function Component
- 尽量使用
useMemo
,避免冗余状态 - 通过依赖注入将函数定义移除组件
- 对于网络请求或 DOM操作,使用 Lazy Evaluation 或 Monad
- 使用 Custom Hook
More
- Functors, Applicatives, And Monads In Pictures - Adit
- HOW TO DEAL WITH DIRTY SIDE EFFECTS IN YOUR PURE FUNCTIONAL JAVASCRIPT - James Sinclair
- Master the JavaScript Interview: What is Functional Programming - Eric Elliott
- JavaScript Monads Made Simple - Eric Elliott
- Procedures, Functions, Data - Brandon Smith’s Blog
- React Hooks简介 - 黯羽轻扬
- 在你身边你左右 —函数式编程别烦恼 - 17点
- 函数式语言的宗教 - 王垠