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
  • 函数式
    • image.png
  • 约束 - 逻辑编程(Prolog)

Pure Function 纯函数

  • 没有副作用(side effect)
    • 副作用:除了将一个值返回(return)给操作的调用者之外,函数还会产生其他的可观察的影响
    • 常见的副作用:操作数据库、网络请求(非幂等)、DOM操作、调用其他副作用函数、给外部变量赋值
  • 返回结果完全由输入决定,与外部状态无关
    • 外部依赖:读外部数据、闭包会隐式地产生状态
  • 引用透明:输出完全由输入决定
  • 并发安全:避免 race condition
  • 纯函数互相调用组装起来的函数,还是纯函数
  • 易读,易懂,易于测试,易于复制和重构。

How to Purify a Function

Dependency Injection 依赖注入

  1. function ProductPage({productId}) {
  2. const [product, setProduct] = useState(null);
  3. const url = 'https://myapi/product/';
  4. async function fetchProduct() {
  5. const response = await fetch(url + productId);
  6. const json = await response.json();
  7. setProduct(json)
  8. }
  9. useEffect(() => {
  10. fetchProduct();
  11. }, [])
  12. }

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

  1. 尽量使用 useMemo,避免冗余状态
  2. 通过依赖注入将函数定义移除组件
  3. 对于网络请求或 DOM操作,使用 Lazy Evaluation 或 Monad
  4. 使用 Custom Hook

More

  1. Functors, Applicatives, And Monads In Pictures - Adit
  2. HOW TO DEAL WITH DIRTY SIDE EFFECTS IN YOUR PURE FUNCTIONAL JAVASCRIPT - James Sinclair
  3. Master the JavaScript Interview: What is Functional Programming - Eric Elliott
  4. JavaScript Monads Made Simple - Eric Elliott
  5. Procedures, Functions, Data - Brandon Smith’s Blog
  6. React Hooks简介 - 黯羽轻扬
  7. 在你身边你左右 —函数式编程别烦恼 - 17点
  8. 函数式语言的宗教 - 王垠